#!/bin/bash

# Library of functions for installation scripts
# Author: crims0n. <http://minios.dev>
#

# ========================= VARIABLES =============================

SET_E=""
SET_U=""

LIVEKITNAME="minios"
BEXT="sb"

common_variables() {
    local locale layout layoutcode

    console_colors
    declare_locales

    # List of variables passed to chroot.
    VARIABLES="BATCH LOGPATH BUILD_TEST_ISO DEBIAN_FRONTEND_TYPE DESKTOP_ENVIRONMENT DISTRIBUTION DISTRIBUTION_TYPE DISTRIBUTION_PHASE PACKAGE_VARIANT COMP_TYPE KERNEL KERNEL_VERSION KERNEL_ARCH KERNEL_TYPE KERNEL_BPO KERNEL_AUFS KERNEL_BUILD_DKMS LIVEKITNAME SYSTEMNAME DISTRIBUTION_ARCH LOCALE LOCALES MULTILINGUAL LAYOUTDSC LAYOUTID TIMEZONE MODULE USER_NAME USER_PASSWORD ROOT_PASSWORD BEXT EXPORT_LOGS"

    #
    CONTAINER_TYPE="0"
    toggle_shell_options u
    if [ -z "${container}" ]; then
        container=""
    fi
    if [ -z "${MODULE}" ]; then
        MODULE=""
    fi
    toggle_shell_options u
    if [[ (-f /.dockerenv || "${container}" = "podman") && ! -f /.minios-live-container ]]; then
        CONTAINER_TYPE="1"
    fi
    if [[ (-f /.dockerenv || "${container}" = "podman") && -f /.minios-live-container ]]; then
        CONTAINER_TYPE="2"
    fi

    if [ "${DISTRIBUTION_ARCH}" = "amd64" ]; then
        ISO_ARCH="amd64"
        KERNEL_ARCH="amd64"
    elif [ "${DISTRIBUTION_ARCH}" = "i386-pae" ]; then
        ISO_ARCH="i386-pae"
        KERNEL_ARCH="686-pae"
        DISTRIBUTION_ARCH="i386"
    elif [ "${DISTRIBUTION_ARCH}" = "i386" ]; then
        ISO_ARCH="i386"
        KERNEL_ARCH="686"
    elif [ "${DISTRIBUTION_ARCH}" = "arm64" ]; then
        ISO_ARCH="arm64"
        KERNEL_ARCH="arm64"
    fi

    if [ "${DESKTOP_ENVIRONMENT}" = "flux" ]; then
        PACKAGE_VARIANT="minimum"
        USER_NAME="root"
    fi

    if [ "${DESKTOP_ENVIRONMENT}" = "cloud" ]; then
        PACKAGE_VARIANT="cloud"
        KERNEL_TYPE="cloud"
    fi

    if [ "${PACKAGE_VARIANT}" = "puzzle" ]; then
        SYSTEM_TYPE="puzzle"
    else
        SYSTEM_TYPE="classic"
    fi

    case "${DISTRIBUTION}" in
    stretch | buster | bullseye | bookworm | trixie | kali-rolling | sid | orel)
        DISTRIBUTION_TYPE="debian"
        ;;
    bionic | focal | jammy | noble)
        DISTRIBUTION_TYPE="ubuntu"
        ;;
    *)
        error "Unknown distribution: ${DISTRIBUTION}"
        exit 1
        ;;
    esac

    case "${DISTRIBUTION}" in
    stretch | buster | orel | bionic)
        DISTRIBUTION_PHASE="legacy"
        ;;
    bullseye | bookworm | focal | jammy | noble)
        DISTRIBUTION_PHASE="current"
        ;;
    trixie | kali-rolling | sid)
        DISTRIBUTION_PHASE="future"
        ;;
    *)
        error "Unknown distribution: ${DISTRIBUTION}"
        exit 1
        ;;
    esac

    # We need to change this.
    if [ "${DISTRIBUTION_TYPE}" = "debian" ]; then
        if [ "${BUILD_FROM_SNAPSHOT}" = "true" ]; then
            DISTRIBUTION_URL="https://snapshot.debian.org/archive/debian/${SNAPSHOT_DATE}"
        else
            DISTRIBUTION_URL="http://ftp.debian.org/debian"
        fi
    elif [ "${DISTRIBUTION_TYPE}" = "ubuntu" ]; then
        DISTRIBUTION_URL="http://archive.ubuntu.com/ubuntu"
    fi
    if [ "${DISTRIBUTION}" = "kali-rolling" ]; then
        DISTRIBUTION_URL="http://archive.kali.org/kali"
    fi

    if [ -d /run/initramfs/memory/bundles ]; then
        BUNDLES=/run/initramfs/memory/bundles
    elif [ -d /memory/bundles ]; then
        BUNDLES=/memory/bundles
    fi

    if [[ -n "${LOCALES[$LOCALE]}" ]]; then
        LAYOUTID=$(echo "${LOCALES[$LOCALE]}" | cut -d',' -f1)
        LAYOUTDSC=$(echo "${LOCALES[$LOCALE]}" | cut -d',' -f2)
    else
        warning "Locale not recognized, defaulting to English (US) layout"
        LAYOUTID="us"
        LAYOUTDSC="English (US)"
    fi

    if [ "${MULTILINGUAL}" = "true" ] || [ "${KEEP_ALL_LOCALES}" = "true" ]; then
        LANGID=""
    else
        LANGID="-$(echo ${LOCALE} | cut -d_ -f1)"
    fi

    if [ "${COMP_TYPE}" = "zstd" ]; then
        ADDITIONAL_COMP_OPTS="-Xcompression-level 19"
    elif [ "${COMP_TYPE}" = "xz" ]; then
        ADDITIONAL_COMP_OPTS="-Xbcj x86"
    else
        ADDITIONAL_COMP_OPTS=""
    fi

    if [ "${PACKAGE_VARIANT}" = "toolbox" ]; then
        # Check if ENABLE_SERVICES contains the ssh service
        if [[ ",${ENABLE_SERVICES}," != *",ssh,"* ]]; then
            # If not, add it
            if [ -z "${ENABLE_SERVICES}" ]; then
                ENABLE_SERVICES="ssh"
            else
                ENABLE_SERVICES="${ENABLE_SERVICES},ssh"
            fi
        fi
    fi
}

# ===================== COMMON FUNCTIONS ==========================

#------- LIBMINIOSLIVE -------

# The `console_colors` function defines a series of variables that correspond to different text colors and styles that can be used in console output.
# Usage:
#   console_colors
#   echo -e "${RED}This is red text${ENDCOLOR}"
#
# The function does not take any arguments, and it needs to be called before using any of the color or style variables in your script.
console_colors() {
    # Standard colors
    RED="\e[31m"
    GREEN="\e[32m"
    YELLOW="\e[33m"
    BLUE="\e[34m"
    MAGENTA="\e[35m"
    CYAN="\e[36m"

    # Dark colors
    DARKGRAY="\e[90m"
    DARKRED="\e[38;5;52m"
    DARKGREEN="\e[38;5;22m"

    # Light colors
    LIGHTGRAY="\e[37m"
    LIGHTRED="\e[91m"
    LIGHTGREEN="\e[92m"
    LIGHTYELLOW="\e[93m"
    LIGHTBLUE="\e[94m"
    LIGHTMAGENTA="\e[95m"
    LIGHTCYAN="\e[96m"

    # Neon Colors
    BRIGHTORANGE="\e[38;5;202m"
    BRIGHTGREEN="\e[38;5;46m"

    # Other colors
    ORANGE="\e[38;5;214m"
    GOLD="\e[38;5;220m"
    PURPLE="\e[38;5;93m"
    PINK="\e[38;5;13m"
    TEAL="\e[38;5;6m"
    NAVY="\e[38;5;18m"

    # Text formatting
    BOLD="\e[1m"
    DIM="\e[2m"
    UNDERLINED="\e[4m"
    BLINK="\e[5m"
    REVERSE="\e[7m"
    HIDDEN="\e[8m"

    # Formatting reset
    ENDCOLOR="\e[0m"
}

# A function to read specific variables from a configuration file in Bash.
# Usage:
#   read_config CONFIG_FILE VAR1 VAR2 [...]
#
# Arguments:
#   CONFIG_FILE - Required. This is the path to your configuration file.
#   VAR1, VAR2, etc - Required. The names of variables you wish to read from the configuration file.
#   Note: It's important to specify the variable names you want to read, as the function won't read any variables if none are specified.
read_config() {
    # Enable extended globbing. This is required for the pattern matching of variable names.
    shopt -s extglob

    # The first argument is the configuration file.
    local CONFIG_FILE="${1}"

    # All other arguments are the variable names to look for.
    local KEYLIST="${@:2}"

    # Check if the configuration file is set, exists and is readable.
    if [[ ! "$CONFIG_FILE" ]]; then
        error "No configuration file given"
        exit 1
    fi
    if [[ ! -f "${CONFIG_FILE}" ]]; then
        error "${CONFIG_FILE} is not a file!"
        exit 1
    fi
    if [[ ! -r "${CONFIG_FILE}" ]]; then
        error "${CONFIG_FILE} is not readable!"
        exit 1
    fi

    # Convert the list of variable names to a regex pattern.
    KEYLIST="${KEYLIST// /|}"

    # Read each line of the file.
    while IFS='= ' read -r LHS RHS; do
        # If the variable name is in our list and the value is not empty...
        if [[ "${LHS}" =~ ^(${KEYLIST})$ ]] && [[ -n ${RHS} ]]; then
            # Remove any quotes around the value.
            RHS="${RHS%\"*}"
            RHS="${RHS#\"*}"
            RHS="${RHS%\'*}"
            RHS="${RHS#\'*}"

            # If the value is an array (surrounded by parentheses)...
            if [[ "${RHS}" =~ ^\((.*)\)$ ]]; then
                # Assign the array to the variable.
                eval ${LHS}=\("${BASH_REMATCH[1]}"\)
            else
                # Otherwise, assign the value to the variable.
                eval ${LHS}=\"${RHS}\"
            fi
        fi
    done <<<"$(tr -d '\r' <${CONFIG_FILE})"

    # Disable extended globbing after we're done using it.
    shopt -u extglob
}

# A function for updating a configuration file in bash.
# Usage:
#   update_config CONFIG_FILE [VAR1] [VAR2] [...]
#
# Arguments:
#   CONFIG_FILE - required, this is the path to your configuration file.
#   VAR1, VAR2, etc - the names of variables you wish to update in the configuration file.
#   If variable names are not provided, the function will update all variables found in the file.
update_config() {
    # The configuration file is passed as the first argument
    local CONFIG_FILE="${1}"
    shift

    # Check if the configuration file is set, exists and is readable.
    if [[ ! "$CONFIG_FILE" ]]; then
        error "No configuration file given"
        exit 1
    fi
    if [[ ! -f "${CONFIG_FILE}" ]]; then
        error "${CONFIG_FILE} is not a file!"
        exit 1
    fi
    if [[ ! -r "${CONFIG_FILE}" ]]; then
        error "${CONFIG_FILE} is not readable!"
        exit 1
    fi

    local -a ARGS

    if (($# > 0)); then
        # Use provided variable names
        ARGS=("$@")
    else
        # Extract variable names from the config file
        ARGS=($(grep -v '^#' "${CONFIG_FILE}" | awk -F '=' '{print $1}'))
    fi

    # Iterate over every variable
    for ARG in "${ARGS[@]}"; do
        local -n VAR="${ARG}"
        local NEW_VALUE ELEMENT

        # If the variable is empty, continue to next
        if [[ -z "${VAR[@]}" ]]; then
            continue
        fi

        # Check if the variable is an array or a simple variable
        case "$(declare -p "${ARG}" 2>/dev/null)" in
        "declare -a"*)
            # If it's an array, construct the new value as an array
            NEW_VALUE="${ARG}=("
            for ELEMENT in "${VAR[@]}"; do
                NEW_VALUE+="\"${ELEMENT}\""
                [[ "${ELEMENT}" != "${VAR[-1]}" ]] && NEW_VALUE+=" "
            done
            NEW_VALUE+=")"
            ;;
        *)
            # If it's a simple variable, construct the new value as a string
            NEW_VALUE="${ARG}=\"${VAR}\""
            ;;
        esac

        # If the variable already exists in the configuration file, replace the old value with the new one
        if grep -q "^${ARG}=" "${CONFIG_FILE}"; then
            sed -i "s|^${ARG}=.*|${NEW_VALUE}|" "${CONFIG_FILE}"
        else
            # If the variable does not exist, append the variable and its value to the end of the configuration file
            echo -e "\n${NEW_VALUE}" >>"${CONFIG_FILE}"
        fi
    done

    # Remove empty lines from the configuration file
    #sed -i '/^$/d' "${CONFIG_FILE}"

    # Sort the lines in the configuration file in alphabetical order
    #sort -o "${CONFIG_FILE}" "${CONFIG_FILE}"
}

# A function to read a specific value from a configuration file in Bash.
# Usage:
#   VAR=$(read_config_value CONFIG_FILE VAR)
#
# Arguments:
#   CONFIG_FILE - Required. This is the path to your configuration file.
#   VAR - Required. The name of the variable you wish to read from the configuration file.
read_config_value() {
    if [ ! -f "$1" ]; then
        echo
        return
    fi
    if grep -q "^$2=" $1; then
        grep "^$2=" $1 | cut -d "=" -f 2- | tail -n 1 | sed -e "s/^['\"]//;s/['\"]$//"
    else
        echo
    fi
}

# only allow 'root' to run the script
allow_root_only() {
    if [ $(id -u) -ne 0 ]; then
        error "This script should be run as 'root'!"
        exit 1
    fi

    export HOME=/root
    export LC_ALL=C
}

# Check the original value of the set options.
determine_option_status() {
    local OPTION="${1}"
    local SET_OPTION="SET_${OPTION^^}"

    if [[ $- == *${OPTION}* ]]; then
        eval "${SET_OPTION}='true'"
    else
        eval "${SET_OPTION}='false'"
    fi
}

# Print the current status of the set options.
print_option_status() {
    local OPTION="${1}"
    local SET_OPTION="SET_${OPTION^^}"

    if [[ $- == *${OPTION}* ]]; then
        information "Option -${OPTION} is currently set."
    else
        information "Option -${OPTION} is currently unset."
    fi
}

# The toggle_shell_options function temporarily toggles Bash shell options for specific code segments.
# Usage: toggle_shell_options "eu"
# This will toggle the -e and -u options.
#
# Cycle in a script:
#   #!/bin/bash
#   set -eu
#   SET_E=""
#   SET_U=""
#   toggle_shell_options "e"  # Disables "e"
#   # Your code
#   toggle_shell_options "e"  # Re-enables "e"
#
# Remember: Declare SET_x variables for each option you plan to toggle. For example, SET_E for "e".
toggle_shell_options() {
    local OPTIONS="${1}"
    for ((i = 0; i < ${#OPTIONS}; i++)); do
        local OPTION="${OPTIONS:$i:1}"
        local SET_OPTION="SET_${OPTION^^}"

        if [ -z "${!SET_OPTION}" ]; then
            determine_option_status "${OPTION}"
            if [ "${!SET_OPTION}" = "true" ]; then
                set "+${OPTION}"
                #print_option_status "${OPTION}"
                continue
            fi
        fi

        if [ "${!SET_OPTION}" = "true" ]; then
            set "-${OPTION}"
            eval "${SET_OPTION}=''"
            #print_option_status "${OPTION}"
        fi
    done
}

# Display an error message.
error() {
    local MESSAGE="${1-}"
    echo -e "${BOLD}${RED}E:${ENDCOLOR} ${MESSAGE}" >&2
}

# Display a warning message.
warning() {
    local MESSAGE="${1-}"
    echo -e "${BOLD}${YELLOW}W:${ENDCOLOR} ${MESSAGE}"
}

# Display an information message.
information() {
    local MESSAGE="${1-}"
    echo -e "${BOLD}${BLUE}I:${ENDCOLOR} ${MESSAGE}"
}

declare_locales() {
    # LOCALES is an associative array where each key-value pair is a locale with its related configurations.
    # Key: Locale code (e.g., "en_US" for American English)
    # Value: A comma-separated string that defines the following:
    #   1. Keyboard layout code (e.g., "us" for U.S. layout)
    #   2. Keyboard layout name (e.g., "English (US)")
    #   3. Firefox locale name in Debian
    #   4. Firefox locale name in Ubuntu (Mozilla repository)
    #   5. LibreOffice locale name in Debian and in Ubuntu
    #   6. LibreOffice LC_MESSAGES
    declare -Ag LOCALES=(
        ["de_DE"]="de,German,de,de,de,de"
        ["en_US"]="us,English (US),,en,,"
        ["es_ES"]="es,Spanish,es-es,es,es,es"
        ["fr_FR"]="fr,French,fr,fr,fr,fr"
        ["it_IT"]="it,Italian,it,it,it,it"
        ["pt_BR"]="br,Portuguese (Brazil),pt-br,pt,pt-br,br"
        ["ru_RU"]="ru,Russian,ru,ru,ru,ru"
    )
}

#------- LIBMINIOSLIVE -------

current_process() {
    echo -e "${LIGHTYELLOW}=====> running ${CYAN}${CMD[ii]}${ENDCOLOR}${LIGHTYELLOW} ...${ENDCOLOR}"
}

current_function() {
    echo -e "=====> ${CYAN}${FUNCNAME[1]}${ENDCOLOR} function is executing ..."
}

# help function
help() {
    # if $1 is set, use $1 as headline message in help()
    if [ -z ${1+x} ]; then
        echo -e "${LIGHTYELLOW}This script builds bootable $SYSTEMNAME ISO image.${ENDCOLOR}"
        echo -e
    else
        echo -e $1
        echo
    fi
    echo -e "Supported commands: ${CYAN}${CMD[*]}${ENDCOLOR}" | fold -w 80 -s
    echo -e
    echo -e "Syntax: ${MAGENTA}$0${ENDCOLOR} [start_cmd] [-] [end_cmd]"
    echo -e "\trun from start_cmd to end_cmd"
    echo -e "\tif start_cmd is omitted, start from first command"
    echo -e "\tif end_cmd is omitted, end with last command"
    echo -e "\tenter single cmd to run the specific command"
    echo -e "\tenter '-' as only argument to run all commands"
    echo -e "\t"
    echo -e "Examples:"
    echo -e "\t${LIGHTYELLOW}$0 -${ENDCOLOR}"
    echo -e "\t${LIGHTYELLOW}$0 build_bootstrap - build_chroot${ENDCOLOR}"
    echo -e "\t${LIGHTYELLOW}$0 - build_chroot${ENDCOLOR}"
    echo -e "\t${LIGHTYELLOW}$0 build_bootstrap -${ENDCOLOR}"
    echo -e "\t${LIGHTYELLOW}$0 build_iso${ENDCOLOR}"
    exit 0
}

# Checks the index of a given command string in the context of global CMD
# array. If the command doesn't exist in CMD, it displays the help.
get_command_index() {
    local i
    for ((i = 0; i < "${#CMD[*]}"; i++)); do
        if [ "${CMD[i]}" == "${1}" ]; then
            INDEX="${i}"
            return
        fi
    done
    help "$(gettext 'Command not found:') ${1}"
}

# Processes script arguments to decide a range, defined by a start
# index and end index, of commands to execute from the CMD array.
determine_command_range() {
    if (($# < 1 || $# > 3)); then
        help
    fi

    DASH_FLAG="false"
    START_INDEX="0"
    END_INDEX="${#CMD[@]}"

    for ARG in "$@"; do
        if [[ "${ARG}" == "-" ]]; then
            DASH_FLAG="true"
            continue
        fi
        get_command_index "${ARG}"
        if [[ "${DASH_FLAG}" == "false" ]]; then
            START_INDEX="${INDEX}"
        else
            END_INDEX=$((INDEX + 1))
        fi
    done

    if [[ "${DASH_FLAG}" == "false" ]]; then
        END_INDEX=$((START_INDEX + 1))
    fi
}

# Check internet connectivity
check_internet_connection() {
    if command -v wget >/dev/null; then
        wget -q --spider https://google.com
    elif command -v curl >/dev/null; then
        curl --silent --head https://google.com
    else
        error "Neither wget nor curl is available on this system."
        exit 1
    fi

    if [ $? -eq 0 ]; then
        information "Internet connection is available."
    else
        error "Internet connection is not available."
        exit 1
    fi
}

# Function to create a log file to record script output
create_log_file() {
    toggle_shell_options u
    if [ -z "${LOG_FILE}" ]; then
        export LOG_FILE="${BUILD_DIR}/build-$(date +%Y%m%d-%H%M%S).log"
        mkdir -p "${BUILD_DIR}"
        ARGS=""
        # Loop over all arguments and add them to ARGS in quotes
        for VAR in "$@"; do
            ARGS="${ARGS}\"${VAR}\" "
        done
        script -q -e -c "${0} ${ARGS}" "${LOG_FILE}"

        # Check if LOG_FILE exists and clean it from escape sequences
        if [ -f "${LOG_FILE}" ]; then
            sed -i 's/\x1B\[[0-9;]*[JKmsu]//g' "${LOG_FILE}"
        fi

        exit $?
    fi
    toggle_shell_options u
}

# check that we are inside chroot
check_is_in_chroot() {
    if [ $(stat -c %i /)="2" ]; then
        error "This script should be run inside chroot only!"
        exit 1
    fi
}

# configure build folder
setup_install_dir() {
    current_function
    WORK_DIR="${BUILD_DIR}/${DISTRIBUTION}-${PACKAGE_VARIANT}-${DISTRIBUTION_ARCH}"
    INSTALL_DIR="${WORK_DIR}/core"
    mkdir -p "${INSTALL_DIR}"

    toggle_shell_options u
    if [ -z "${APTCACHE_DIR}" ]; then
        APTCACHE_DIR="${WORK_DIR}/aptcache"
    fi
    if [ -z "${ROOTFS_TARBALL}" ]; then
        ROOTFS_TARBALL="${WORK_DIR}/${DISTRIBUTION}-${DISTRIBUTION_ARCH}-rootfs.tar.gz"
    fi
    toggle_shell_options u

    generate_chroot_configuration_file "${WORK_DIR}/minios-build.conf"
    EXCLUDE_CORE_FILE="${WORK_DIR}/squashfs-core-exclude"
    #generate_squashfs_exclude_file "${EXCLUDE_CORE_FILE}" "${INSTALL_DIR}"
}

# Unmount directories
unmount_dirs() {
    current_function

    local DIR_PATH="$1"
    local ATTEMPTS="0"
    local DIR UNMOUNTED
    while true; do
        UNMOUNTED="true"
        for DIR in $(mount | grep ${DIR_PATH} | awk '{print $3}'); do
            umount "${DIR}" 2>/dev/null || UNMOUNTED="false"
        done
        if [ "${UNMOUNTED}" = "true" ]; then
            information "The file systems inside the ${DIR_PATH} directory are unmounted."
            break
        fi
        sleep 1
        ATTEMPTS=$(("${ATTEMPTS}" + 1))
        if [ "${ATTEMPTS}" -ge 5 ]; then
            error "Failed to unmount directories after 5 attempts."
            break
        fi
    done

    if [ "${UNMOUNTED}" = "false" ]; then
        error "Failed to unmount all file systems. Unmount file systems inside ${DIR_PATH} manually and try again."
        exit 1
    fi
}

directory_cleanup() {
    current_function

    local TARGET_DIR=""
    if [ "${CMD[ii]}" = "build_bootstrap" ]; then
        TARGET_DIR="${INSTALL_DIR}"
    elif [ "${CMD[ii]}" = "remove_sources" ] && [ "$REMOVE_SOURCES" = "true" ]; then
        TARGET_DIR="${WORK_DIR}"
    fi

    if [ -n "${TARGET_DIR}" ]; then
        unmount_dirs "${TARGET_DIR}"
        information "Deleting directory ${TARGET_DIR}"
        if [ -d "${TARGET_DIR}" ]; then
            rm -rf "${TARGET_DIR}"
            if [ $? -eq 0 ]; then
                information "Successfully deleted ${TARGET_DIR}"
            else
                error "Failed to delete directory ${TARGET_DIR}"
                exit 1
            fi
        else
            warning "Directory ${TARGET_DIR} does not exist, thus not removed."
        fi
        mkdir -p "${TARGET_DIR}"
    fi
}

setup_chroot_environment() {
    local DIR_PATH="$1"

    for DIR in dev run proc sys tmp; do
        mkdir -p "${DIR_PATH}/${DIR}"
    done

    mount --bind /dev "${DIR_PATH}/dev"
    mount --bind /run "${DIR_PATH}/run"
    mount none -t proc "${DIR_PATH}/proc"
    mount none -t sysfs "${DIR_PATH}/sys"
    mount none -t devpts "${DIR_PATH}/dev/pts"
    mount none -t tmpfs "${DIR_PATH}/tmp"

    mkdir -p "${APTCACHE_DIR}" "${DIR_PATH}/var/cache/apt/archives"
    mount --bind "${APTCACHE_DIR}" "${DIR_PATH}/var/cache/apt/archives"
}

update_resolv_conf() {
    local DIR_PATH="${1}"
    local RESOLV_PATH="${DIR_PATH}/etc/resolv.conf"
    local BACKUP_PATH="${RESOLV_PATH}.bak"

    if [ -f "/.dockerenv" ] || [ "${container}" = "podman" ] || [ "${DISTRIBUTION_TYPE}" = "ubuntu" ]; then
        if [ -L "${RESOLV_PATH}" ]; then
            mv "${RESOLV_PATH}" "${BACKUP_PATH}"
            echo "nameserver 8.8.8.8" >"${RESOLV_PATH}"
        elif [ -e "${BACKUP_PATH}" ]; then
            mv "${BACKUP_PATH}" "${RESOLV_PATH}"
        fi
    fi
}

# mount filesystems inside chroot
chroot_mount_fs() {
    current_function

    unmount_dirs "${WORK_DIR}"

    setup_chroot_environment "${INSTALL_DIR}"

    update_resolv_conf "${INSTALL_DIR}"
}

# Displaying information about the start of the build for a more
# convenient search in the logs
new_run() {
    DATE=$(date +"%Y.%m.%d %H:%M")
    echo ""
    echo "================================================================="
    echo "================================================================="
    echo "============================ NEW RUN ============================"
    echo "======================== $DATE ======================="
    echo "================================================================="
    echo "======= If during the installation you get an error that ========"
    echo "=== /dev/stdout is not available, try using a bionic or xenial =="
    echo "================= based container to install. ==================="
    echo "================================================================="
    echo "================================================================="
    echo "========== Distributution: ${DISTRIBUTION}"
    echo "========== Desktop environment: ${DESKTOP_ENVIRONMENT}"
    echo "========== Package variant: ${PACKAGE_VARIANT}"
    echo "========== Arch: ${DISTRIBUTION_ARCH}"
    echo "========== Kernel type: ${KERNEL_TYPE}"
    echo "========== Kernel BPO: ${KERNEL_BPO}"
    echo "========== Kernel AUFS: ${KERNEL_AUFS}"
    echo "========== Install additional drivers: ${KERNEL_BUILD_DKMS}"
    echo "========== Named boot files: ${NAMED_BOOT_FILES}"
    echo "========== Compression: ${COMP_TYPE}"
    echo "========== Locale: ${LOCALE}"
    echo "========== Time zone: ${TIMEZONE}"
    echo "================================================================="
    echo "================================================================="
    echo ""
}

generate_chroot_configuration_file() {
    toggle_shell_options u
    cat <<EOF >"${1}"
BEXT="${BEXT}"
BUILD_TEST_ISO="${BUILD_TEST_ISO}"
COMP_TYPE="${COMP_TYPE}"
DEBIAN_FRONTEND_TYPE="${DEBIAN_FRONTEND_TYPE}"
DESKTOP_ENVIRONMENT="${DESKTOP_ENVIRONMENT}"
DISTRIBUTION="${DISTRIBUTION}"
DISTRIBUTION_ARCH="${DISTRIBUTION_ARCH}"
DISTRIBUTION_TYPE="${DISTRIBUTION_TYPE}"
DISTRIBUTION_PHASE="${DISTRIBUTION_PHASE}"
KERNEL="${KERNEL}"
KERNEL_ARCH="${KERNEL_ARCH}"
KERNEL_AUFS="${KERNEL_AUFS}"
KERNEL_BPO="${KERNEL_BPO}"
KERNEL_BUILD_ARCH="${KERNEL_BUILD_ARCH}"
KERNEL_BUILD_DKMS="${KERNEL_BUILD_DKMS}"
KERNEL_TYPE="${KERNEL_TYPE}"
KERNEL_VERSION="${KERNEL_VERSION}"
LAYOUTDSC="${LAYOUTDSC}"
LAYOUTID="${LAYOUTID}"
LIVEKITNAME="${LIVEKITNAME}"
LOCALE="${LOCALE}"
MODULE="${MODULE}"
MULTILINGUAL="${MULTILINGUAL}"
KEEP_ALL_LOCALES="${KEEP_ALL_LOCALES}"
PACKAGE_VARIANT="${PACKAGE_VARIANT}"
SYSTEMNAME="${SYSTEMNAME}"
TIMEZONE="${TIMEZONE}"
USER_NAME="${USER_NAME}"
EOF
    toggle_shell_options u
}

generate_squashfs_exclude_file() {
    cat <<EOF >"${1}"
${2}/boot
${2}/root/.bash_history
${2}/root/.cache
${2}/root/.local/share/mc
${2}/root/.wget-hsts
${2}/var/lib/apt/extended_states
${2}/var/lib/dhcp/dhclient.leases
${2}/var/lib/systemd/random-seed
${2}/initrd.img
${2}/initrd.img.old
${2}/vmlinuz
${2}/vmlinuz.old
${2}/minios-build.conf
${2}/minioslib
${2}/linux-live
${2}/install
${2}/build
EOF
}

# This function creates two configuration files for chroot environment
# The first file contains various environment variables and their values
# The second file contains several utility functions
add_chroot_configuration_files() {
    generate_chroot_configuration_file "${1}/minios-build.conf"
    if [ -f "${SCRIPT_DIR}/linux-live/minioslib" ]; then
        cp "${SCRIPT_DIR}/linux-live/minioslib" "$1/minioslib"
    elif [ -f "/linux-live/minioslib" ]; then
        cp "/linux-live/minioslib" "$1/minioslib"
        #elif [ -f "/usr/lib/minios/libminioslive" ]; then
        #   cp "/usr/lib/minios/libminioslive" "$1/minioslib"
    fi
}

remove_chroot_configuration_files() {
    rm -f "$1/minios-build.conf"
    rm -f "$1/minioslib"
}

timezone_to_array() {
    local IFS
    IFS="/"
    TIMEZONE=(${TIMEZONE})
}

pkg() {
    local PACKAGE_MANAGER INSTALL_OPTIONS COMMAND

    if command -v apt-get >/dev/null 2>&1; then
        PACKAGE_MANAGER="apt-get"
    elif command -v apt >/dev/null 2>&1; then
        PACKAGE_MANAGER="apt"
    else
        error "Error: Could not find a supported package manager" >&2
        exit 1
    fi

    INSTALL_OPTIONS="--yes"
    COMMAND="$1"
    shift

    case "${COMMAND}" in
    install | remove | upgrade | autoremove | purge | clean | build-dep | dist-upgrade | source)
        DEBIAN_FRONTEND="${DEBIAN_FRONTEND_TYPE}" "${PACKAGE_MANAGER}" "${INSTALL_OPTIONS}" "${COMMAND}" "$@"
        ;;
    update | edit-sources | moo | download | check)
        DEBIAN_FRONTEND="${DEBIAN_FRONTEND_TYPE}" "${PACKAGE_MANAGER}" "${COMMAND}" "$@"
        ;;
    *)
        error "Invalid command: ${COMMAND}" >&2
        exit 1
        ;;
    esac
}

# ====================== HOST FUNCTIONS ===========================

# Creating a $PACKAGE_VARIANT list from a template
create_apt_list() {
    current_function
    if [ "${BUILD_FROM_SNAPSHOT}" = "true" ]; then
        if [ "${DISTRIBUTION}" = "sid" ] || [ "${DISTRIBUTION}" = "bookworm" ]; then
            echo "deb     https://snapshot.debian.org/archive/debian/${SNAPSHOT_DATE}/ ${DISTRIBUTION} main contrib non-free" >"${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}-snapshot.list"
            echo "#deb-src https://snapshot.debian.org/archive/debian/${SNAPSHOT_DATE}/ ${DISTRIBUTION} main contrib non-free" >>"${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}-snapshot.list"
        else
            cp -f "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION_TYPE}-snapshot.list" "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}-snapshot.list"
            sed -i "s,distro,${DISTRIBUTION},g;s,datetime,${SNAPSHOT_DATE},g" "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}-snapshot.list"
        fi
    else
        if [ ! -f "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}.list" ]; then
            cp -f "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION_TYPE}.list" "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}.list"
            sed -i "s,distro,${DISTRIBUTION},g" "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}.list"
            sed -i "s,http://archive.ubuntu.com/ubuntu,${DISTRIBUTION_URL},g" "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}.list"
        fi
    fi
}

export_chroot_variables() {
    current_function
    add_chroot_configuration_files /
    . /minioslib
    read_config /minios-build.conf "${VARIABLES}"
    export ${VARIABLES}
}

chroot_run() {
    current_function
    add_chroot_configuration_files "${1}"
    chroot "${1}" /bin/bash <<EOF
. /minioslib
read_config /minios-build.conf "${VARIABLES}"
export ${VARIABLES}
${@:2}
EOF
}

# Unzip gzipped files (man pages), so LZMA can compress 2times better.
# First we fix symlinks, then uncompress files
# $1 = search directory
uncompress_files() {
    current_function
    local LINK LINE

    find "${1}" -type l -name "*.gz" | while read LINE; do
        LINK="$(readlink "${LINE}" | sed -r 's/.gz$//')"
        FILE="$(echo "${LINE}" | sed -r 's/.gz$//')"
        ln -sfn "${LINK}" "${FILE}"
        rm -f "${LINE}"
    done
    find "${1}" -type f -name "*.gz" | xargs -r gunzip 2>/dev/null
}
# remove broken links
# $1 = search directory
remove_broken_links() {
    current_function
    find "${1}" -type l -exec test ! -e {} \; -print | xargs rm -vf
}

# Installing the base system
extract_tarball() {
    local TARBALL="${1}"
    local DESTINATION="${2}"
    if [ ! -f "${TARBALL}" ]; then
        error "File ${TARBALL} not found!"
        exit 1
    fi
    mkdir -p "${DESTINATION}"
    tar -xzf "${TARBALL}" -C "${DESTINATION}"
    if [ $? -ne 0 ]; then
        error "Error extracting tarball ${TARBALL}!"
        exit 1
    fi
}

build_bootstrap() {
    current_process

    local DEBOOTSTRAP_INCLUDE="--include=apt-transport-https --include=ca-certificates --include=wget"

    if [ "${CONTAINER_TYPE}" = "1" ]; then
        if [ ! -f /.minios-live ]; then
            setup_host
        fi
    fi

    directory_cleanup

    if [ "${USE_ROOTFS}" = "true" ]; then
        if [ -f "${ROOTFS_TARBALL}" ]; then
            extract_tarball "${ROOTFS_TARBALL}" "${INSTALL_DIR}"
        else
            mkdir -p "${APTCACHE_DIR}" "${INSTALL_DIR}/var/cache/apt/archives"
            mount --bind "${APTCACHE_DIR}" "${INSTALL_DIR}/var/cache/apt/archives"

            sudo DEBIAN_FRONTEND="${DEBIAN_FRONTEND_TYPE}" \
                debootstrap --arch="${DISTRIBUTION_ARCH}" ${DEBOOTSTRAP_INCLUDE} "${DISTRIBUTION}" "${INSTALL_DIR}" "${DISTRIBUTION_URL}"

            umount "${INSTALL_DIR}/var/cache/apt/archives"

            mkdir -p "${SCRIPT_DIR}/rootfs"
            tar -czf "${ROOTFS_TARBALL}" -C "${INSTALL_DIR}" .
        fi
    else
        sudo DEBIAN_FRONTEND="${DEBIAN_FRONTEND_TYPE}" \
            debootstrap --arch="${DISTRIBUTION_ARCH}" ${DEBOOTSTRAP_INCLUDE} "${DISTRIBUTION}" "${INSTALL_DIR}" "${DISTRIBUTION_URL}"
    fi

    if [ "${DISTRIBUTION}" = "kali-rolling" ]; then
        DISTRIBUTION="testing"
        create_apt_list
        cp -f "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}.list" "${INSTALL_DIR}/etc/apt/sources.list"
        chroot_run "${INSTALL_DIR}" pkg update
        chroot_run "${INSTALL_DIR}" pkg install gnupg
        chroot_run "${INSTALL_DIR}" apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ED444FF07D8D0BF6
        DISTRIBUTION="kali-rolling"
    fi

    create_apt_list

    if [ "${BUILD_FROM_SNAPSHOT}" = "true" ]; then
        cp -f "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}-snapshot.list" "${INSTALL_DIR}/etc/apt/sources.list"
        echo 'Acquire::Check-Valid-Until "false";' | sudo tee "${INSTALL_DIR}/etc/apt/apt.conf.d/00snapshot"
    else
        cp -f "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}.list" "${INSTALL_DIR}/etc/apt/sources.list"
    fi
}

copy_build_scripts() {
    current_function

    local DESTINATION=${1:-$INSTALL_DIR}

    mkdir -p "${DESTINATION}/linux-live"
    rsync -a --exclude='.git' "${SCRIPT_DIR}/linux-live/" "${DESTINATION}/linux-live/"

    chmod +x "${DESTINATION}/linux-live/install-chroot"
}

build_chroot() {
    current_process

    if [ "${CONTAINER_TYPE}" = "1" ]; then
        if [ ! -f /.minios-live ]; then
            setup_host
        fi
    fi

    chroot_mount_fs "${INSTALL_DIR}"

    copy_build_scripts

    create_apt_list

    if [ "${BUILD_FROM_SNAPSHOT}" = "true" ]; then
        cp -f "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}-snapshot.list" "${INSTALL_DIR}/etc/apt/sources.list"
    else
        cp -f "${SCRIPT_DIR}/linux-live/aptsources/${DISTRIBUTION}.list" "${INSTALL_DIR}/etc/apt/sources.list"
    fi

    if [ -f /.minios-live-container ]; then
        chroot_run "${INSTALL_DIR}" /linux-live/install-chroot -
    else
        chroot "${INSTALL_DIR}" /linux-live/install-chroot -
    fi

    unmount_dirs "${WORK_DIR}"
}

mkmod_corefs() {
    local FOLDER
    cd "${1}"
    COREFS=""
    # List of directories for root filesystem
    # No subdirectories are allowed, no slashes,
    # so You can't use /var/tmp here for example
    # Exclude directories like proc sys tmp
    MKMOD="bin etc home lib lib64 opt root sbin srv usr var"
    for FOLDER in ${MKMOD}; do
        if [ -d "${1}/${FOLDER}" ]; then
            COREFS="${COREFS} ${FOLDER}"
        fi
    done
}

build_live() {
    current_process

    if [ "${CONTAINER_TYPE}" = "1" ]; then
        if [ ! -f "/.minios-live" ]; then
            setup_host
        fi
    fi

    copy_build_scripts

    rm -rf "${WORK_DIR}/image"
    mkdir -p "${WORK_DIR}/image/${LIVEKITNAME}"/{boot,changes,modules}

    # create compressed 00-core.sb
    mkmod_corefs "${INSTALL_DIR}"

    time mksquashfs ${COREFS} "${WORK_DIR}/image/${LIVEKITNAME}/00-core-${DISTRIBUTION_ARCH}-${COMP_TYPE}.${BEXT}" -comp "${COMP_TYPE}" ${ADDITIONAL_COMP_OPTS} -b 1024K -always-use-fragments -noappend -quiet -progress || exit 1

}

build_config() {
    current_process

    if [ -d "${WORK_DIR}/image/${LIVEKITNAME}" ]; then
        cat <<EOF >"${WORK_DIR}/image/${LIVEKITNAME}/${LIVEKITNAME}.conf"
USER_NAME="${USER_NAME}"
USER_PASSWORD="${USER_PASSWORD}"
ROOT_PASSWORD="${ROOT_PASSWORD}"
HOST_NAME="${LIVEKITNAME}"
DEFAULT_TARGET="${DEFAULT_TARGET}"
ENABLE_SERVICES="${ENABLE_SERVICES}"
DISABLE_SERVICES="${DISABLE_SERVICES}"
SSH_KEY="${SSH_KEY}"
SCRIPTS="${SCRIPTS}"
HIDE_CREDENTIALS="${HIDE_CREDENTIALS}"
AUTOLOGIN="${AUTOLOGIN}"
LINK_USER_DIRS="${LINK_USER_DIRS}"
SYSTEM_TYPE="${SYSTEM_TYPE}"
EXPORT_LOGS="${EXPORT_LOGS}"
EOF
    fi
}

create_config_files() {
    DEFAULT_SETTINGS="load_ramdisk=1 prompt_ramdisk=0 rw printk.time=0 consoleblank=0 net.ifnames=0 biosdevname=0"
    DEBUG_SETTINGS="load_ramdisk=1 prompt_ramdisk=0 rw printk.time=0 debug net.ifnames=0 biosdevname=0"

    BOOT_SETTINGS="quiet"

    FLUX_SETTINGS=""
    ULTRA_SETTINGS=""

    if [ "${DESKTOP_ENVIRONMENT}" = "flux" ]; then
        FLUX_SETTINGS=" automount"
    elif [ "${PACKAGE_VARIANT}" = "ultra" ]; then
        ULTRA_SETTINGS=" apparmor=0 selinux=0"
    fi

    cat <<EOF | iconv -f 'UTF-8' -t "CP866//TRANSLIT" >"${WORK_DIR}/image/${LIVEKITNAME}/boot/syslinux.cfg"
UI vesamenu.c32

PROMPT 0
TIMEOUT 100

MENU CLEAR
MENU HIDDEN
MENU HIDDENKEY Enter default
MENU BACKGROUND bootlogo.png
MENU RESOLUTION 1024 768

MENU WIDTH 125
MENU MARGIN 20
MENU ROWS 5
MENU TABMSGROW 14
MENU CMDLINEROW 12
MENU HSHIFT 0
MENU VSHIFT 32

MENU COLOR BORDER  30;40      #00000000 #00000000 none
MENU COLOR SEL     47;30      #FF000000 #99FFFFFF none
MENU COLOR UNSEL   37;40      #99FFFFFF #FF000000 none
MENU COLOR TABMSG 32;40 #FFA500 #FF000000 none

F1 help.txt zblack.png

MENU AUTOBOOT Press Esc for options, automatic boot in # second{,s} ...
MENU TABMSG [F1] help                                                        [Tab] cmdline >

LABEL default
MENU LABEL Run MiniOS (Resume previous session)
KERNEL /${LIVEKITNAME}/boot/vmlinuz
APPEND vga=791 initrd=/${LIVEKITNAME}/boot/initrfs.img ${DEFAULT_SETTINGS} ${BOOT_SETTINGS}${FLUX_SETTINGS}${ULTRA_SETTINGS} perchdir=resume

LABEL perch
MENU LABEL Run MiniOS (Start a new session)
KERNEL /${LIVEKITNAME}/boot/vmlinuz
APPEND vga=791 initrd=/${LIVEKITNAME}/boot/initrfs.img ${DEFAULT_SETTINGS} ${BOOT_SETTINGS}${FLUX_SETTINGS}${ULTRA_SETTINGS} perchdir=new

LABEL asksession
MENU LABEL Run MiniOS (Choose session during startup)
KERNEL /${LIVEKITNAME}/boot/vmlinuz
APPEND vga=791 initrd=/${LIVEKITNAME}/boot/initrfs.img ${DEFAULT_SETTINGS} ${BOOT_SETTINGS}${FLUX_SETTINGS}${ULTRA_SETTINGS} perchdir=ask

LABEL live
MENU LABEL Run MiniOS (Fresh start)
KERNEL /${LIVEKITNAME}/boot/vmlinuz
APPEND vga=791 initrd=/${LIVEKITNAME}/boot/initrfs.img ${DEFAULT_SETTINGS} ${BOOT_SETTINGS}${FLUX_SETTINGS}${ULTRA_SETTINGS}

LABEL toram
MENU LABEL Run MiniOS (Copy to RAM)
KERNEL /${LIVEKITNAME}/boot/vmlinuz
APPEND vga=791 initrd=/${LIVEKITNAME}/boot/initrfs.img ${DEFAULT_SETTINGS} ${BOOT_SETTINGS}${ULTRA_SETTINGS} toram

EOF

    cat <<EOF >"${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/grub.cfg"
set default=0
set timeout=10
set message="Loading kernel and ramdisk..."

loadfont \$prefix/dejavu-bold-16.pf2
loadfont \$prefix/dejavu-bold-14.pf2
loadfont \$prefix/roboto-bold-20.pf2
loadfont \$prefix/roboto-regular-20.pf2
loadfont \$prefix/unicode.pf2

if [ "\$grub_platform" = "pc" ]; then
    set gfxmode=1024x768x32
    set vga="vga=791"
else
    set gfxmode=auto
fi

insmod all_video
insmod gfxterm
insmod png

set color_normal=light-gray/black
set color_highlight=white/black

if [ -e /minios/boot/bootlogo.png ]; then
    set theme=/minios/boot/grub/minios-theme/theme.txt
else
    set color_normal=white/black
    set color_highlight=black/white
fi

terminal_output gfxterm

insmod play
play 960 440 1 0 4 440 1

set default_settings="${DEFAULT_SETTINGS}"
set console_settings="console=tty0 console=ttyS0,115200"
set debug_settings="${DEBUG_SETTINGS}"
set linux_image="/${LIVEKITNAME}/boot/vmlinuz"
set initrd_img="/${LIVEKITNAME}/boot/initrfs.img"

menuentry "Run MiniOS (Resume previous session)" --class resume {
    echo \$message
    search --set -f /${LIVEKITNAME}/boot/vmlinuz
    linux /${LIVEKITNAME}/boot/vmlinuz ${DEFAULT_SETTINGS} ${BOOT_SETTINGS}${FLUX_SETTINGS}${ULTRA_SETTINGS} perchdir=resume
    initrd /${LIVEKITNAME}/boot/initrfs.img
}
menuentry "Run MiniOS (Start a new session)" --class new {
    echo \$message
    search --set -f /${LIVEKITNAME}/boot/vmlinuz
    linux /${LIVEKITNAME}/boot/vmlinuz ${DEFAULT_SETTINGS} ${BOOT_SETTINGS}${FLUX_SETTINGS}${ULTRA_SETTINGS} perchdir=new
    initrd /${LIVEKITNAME}/boot/initrfs.img
}
menuentry "Run MiniOS (Choose session during startup)" --class switch {
    echo \$message
    search --set -f /${LIVEKITNAME}/boot/vmlinuz
    linux /${LIVEKITNAME}/boot/vmlinuz ${DEFAULT_SETTINGS} ${BOOT_SETTINGS}${FLUX_SETTINGS}${ULTRA_SETTINGS} perchdir=ask
    initrd /${LIVEKITNAME}/boot/initrfs.img
}
menuentry "Run MiniOS (Fresh start)" --class live {
    echo \$message
    search --set -f /${LIVEKITNAME}/boot/vmlinuz
    linux /${LIVEKITNAME}/boot/vmlinuz ${DEFAULT_SETTINGS} ${BOOT_SETTINGS}${FLUX_SETTINGS}${ULTRA_SETTINGS}
    initrd /${LIVEKITNAME}/boot/initrfs.img
}
menuentry "Run MiniOS (Copy to RAM)" --class ram {
    echo \$message
    search --set -f /${LIVEKITNAME}/boot/vmlinuz
    linux /${LIVEKITNAME}/boot/vmlinuz ${DEFAULT_SETTINGS} ${BOOT_SETTINGS}${FLUX_SETTINGS}${ULTRA_SETTINGS} toram
    initrd /${LIVEKITNAME}/boot/initrfs.img
}

EOF
}

create_readme() {
    cat <<EOF >"${WORK_DIR}/image/README"
################################### README ####################################
* To install MiniOS, copy the contents of the ISO to the root of the media,
  then run ${LIVEKITNAME}\boot\bootinst.bat on Windows, or ${LIVEKITNAME}/boot/bootinst.sh
  on Linux.
* When installed on media with fat32 and ntfs file systems, changes in
  persistent changes mode will be saved in the ${LIVEKITNAME}\changes folder,
  dat files expands dynamically, file size can be changed
  with the perchsize parameter in the boot options, for example
  perchsize=4000 will set the maximum file size to 4 GB.
* When installed on media with ext2-ext4, xfs, btrfs file systems, changes in
  the persistent changes mode will be saved in the ${LIVEKITNAME}\changes folder with
  no size limit.
###############################################################################
EOF
}

build_boot() {
    current_process

    local OLD_KERNEL

    if [ "${CONTAINER_TYPE}" = "1" ]; then
        if [ ! -f "/.minios-live" ]; then
            setup_host
        fi
    fi

    copy_build_scripts

    cp -r "${SCRIPT_DIR}/linux-live/bootfiles/"* "${WORK_DIR}/image/${LIVEKITNAME}"

    if [ "${DISTRIBUTION_ARCH}" = "amd64" ]; then
        ARCH="x86_64"
        rm -rf "${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/i386-efi"
        rm "${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/efi32.img"
        rm "${WORK_DIR}/image/${LIVEKITNAME}/boot/EFI/boot/"*32.efi
        mv "${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/efi64.img" "${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/efi.img"
    elif [[ "${DISTRIBUTION_ARCH}" == *"i386"* ]]; then
        ARCH="i386"
        rm -rf "${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/x86_64-efi"
        rm "${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/efi64.img"
        rm "${WORK_DIR}/image/${LIVEKITNAME}/boot/EFI/boot/"*64.efi
        mv "${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/efi32.img" "${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/efi.img"
    fi

    cat <<EOF >"${WORK_DIR}/image/${LIVEKITNAME}/boot/EFI/debian/grub.cfg"
search --file --set=root /.disk/info
set prefix=(\$root)/minios/boot/grub
source \$prefix/${ARCH}-efi/grub.cfg
EOF

    cat <<EOF >"${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/${ARCH}-efi/grub.cfg"
insmod part_acorn
insmod part_amiga
insmod part_apple
insmod part_bsd
insmod part_dfly
insmod part_dvh
insmod part_gpt
insmod part_msdos
insmod part_plan
insmod part_sun
insmod part_sunpc
source /${LIVEKITNAME}/boot/grub/grub.cfg
EOF

    mkdir -p "${WORK_DIR}/image/EFI"
    cp -r "${WORK_DIR}/image/${LIVEKITNAME}/boot/EFI/"* "${WORK_DIR}/image/EFI"

    mkdir -p "${WORK_DIR}/image/.disk"
    echo "${SYSTEMNAME}" >"${WORK_DIR}/image/.disk/info"

    create_config_files

    create_readme

    if [[ "${MODULE}" == *"kernel"* ]] || [[ "${MODULE}" == *"linux"* ]]; then
        # kernel.conf generation is in linux-live/scripts/01-kernel/build
        [ -f "${WORK_DIR}/minios-build.conf" ] && read_config "${WORK_DIR}/minios-build.conf" KERNEL

        copy_build_scripts "${MODULE_MERGED_DIR}"
        chmod +x "${MODULE_MERGED_DIR}/linux-live/build-initramfs"
        chroot_run "${MODULE_MERGED_DIR}" "/linux-live/build-initramfs"

        # copy boot files
        kernel_variables
        cp "${MODULE_MERGED_DIR}/boot/vmlinuz"-* "${WORK_DIR}/image/${LIVEKITNAME}/boot/${VMLINUZNAME}"
        cp "${MODULE_MERGED_DIR}/boot/initrfs.img" "${WORK_DIR}/image/${LIVEKITNAME}/boot/${INITRFSNAME}"

        for CFG_FILE in syslinux.cfg grub/grub.cfg; do
            sed -i "s,/boot/vmlinuz,/boot/${VMLINUZNAME},g;s,/boot/initrfs.img,/boot/${INITRFSNAME},g" "${WORK_DIR}/image/${LIVEKITNAME}/boot/${CFG_FILE}"
        done

        [ -d "${MODULE_MERGED_DIR}/linux-live" ] && rm -rf "${MODULE_MERGED_DIR}/linux-live"
    fi
}

build_iso() {
    current_process

    local DIR

    if [ "${CONTAINER_TYPE}" = "1" ] && [ ! -f "/.minios-live" ]; then
        setup_host
    fi

    cd "${WORK_DIR}/image"
    mkdir -p "${ISO_DIR}"

    DIR="${WORK_DIR}/image"

    if [ "${DESKTOP_ENVIRONMENT}" = "xfce-ultra" ] || [ "${DESKTOP_ENVIRONMENT}" = "xfce-puzzle" ] || [ "${DESKTOP_ENVIRONMENT}" = "xfce-puzzle-base" ]; then
        IMAGE="${LIVEKITNAME}-${DISTRIBUTION}-${DESKTOP_ENVIRONMENT}${LANGID}"
    else
        IMAGE="${LIVEKITNAME}-${DISTRIBUTION}-${DESKTOP_ENVIRONMENT}-${PACKAGE_VARIANT}${LANGID}"
    fi
    if [ "${KERNEL_AUFS}" = "true" ]; then
        IMAGE="${IMAGE}-aufs"
    fi
    if [ "${KERNEL_TYPE}" = "default" ]; then
        IMAGE="${IMAGE}-${ISO_ARCH}"
    else
        IMAGE="${IMAGE}-${KERNEL_TYPE}-${ISO_ARCH}"
    fi
    if [ "${BUILD_FROM_SNAPSHOT}" = "true" ]; then
        IMAGE="${IMAGE}-${SNAPSHOT_DATE}"
    fi

    ISO="${ISO_DIR}/${IMAGE}-${COMP_TYPE}-$(date +%Y%m%d_%H%M).iso"

    if [ "${REMOVE_OLD_ISO}" = "true" ]; then
        toggle_shell_options e
        rm "${ISO_DIR}/${IMAGE}-${COMP_TYPE}-"*".iso"
        while [ -f "${ISO_DIR}/${LIVEKITNAME}.iso" ]; do
            rm "${ISO_DIR}/${LIVEKITNAME}.iso"
            sleep 1
        done
        toggle_shell_options e
    fi

    xorriso \
        --as mkisofs \
        --isohybrid-mbr "${DIR}/${LIVEKITNAME}/boot/isohdpfx.bin" \
        --hide-rr-moved \
        -f \
        -r \
        --joliet \
        -l \
        -V "${SYSTEMNAME}" \
        -A "${SYSTEMNAME}" \
        -b "${LIVEKITNAME}/boot/isolinux.bin" \
        -c "${LIVEKITNAME}/boot/isolinux.boot" \
        --no-emul-boot \
        --boot-load-size 4 \
        --boot-info-table \
        --eltorito-alt-boot \
        --isohybrid-gpt-basdat \
        --efi-boot "${LIVEKITNAME}/boot/grub/efi.img" \
        --no-emul-boot \
        --output "${ISO}" "${DIR}"

    if [ $? = "0" ]; then
        echo ">>> ${ISO} created"
    else
        exit 1
    fi

    if [ "${BUILD_TEST_ISO}" = "true" ]; then
        if [ -f "${ISO_DIR}/${LIVEKITNAME}.iso" ]; then
            rm -f "${ISO_DIR}/${LIVEKITNAME}.iso"
        fi
        ln "${ISO}" "${ISO_DIR}/${LIVEKITNAME}.iso" && echo ">>> ${ISO_DIR}/${LIVEKITNAME}.iso created"
    fi
}

# If the REMOVE_SOURCES is set to "true", removes all sources
remove_sources() {
    current_function
    if [ "${REMOVE_SOURCES}" = "true" ]; then
        directory_cleanup
    fi
}

# ====================== INSTALL FUNCTIONS ========================

# Sets up the host by installing and configuring necessary packages, takes into account the container type
setup_host() {
    if [ "${SKIP_SETUP_HOST}" != "true" ]; then
        current_process

        if [ "${CONTAINER_TYPE}" != "2" ]; then
            hostreq_pkg_list
        fi
    fi
}

# Sets up the host within a chroot environment, dealing with hostname, pre-requesite packages and machine-id settings.
chroot_setup_host() {
    current_process

    echo "${LIVEKITNAME}" >/etc/hostname

    install_prerequesited_packages

    #configure machine id
    dbus-uuidgen >/etc/machine-id
    ln -fs /etc/machine-id /var/lib/dbus/machine-id

    # don't understand why, but multiple sources indicate this
    dpkg-divert --local --rename --add /sbin/initctl
    ln -s /bin/true /sbin/initctl
}

get_base_path() {
    local BASE_PATH=""
    if [ "${1}" == "core" ]; then
        BASE_PATH=""
    elif [ "${1}" == "module" ]; then
        BASE_PATH="${MODULE_UPPER_DIR}"
    else
        error "Invalid mode. Choose either 'core' or module."
        exit 1
    fi
    echo "${BASE_PATH}"
}

# Performs cleanup within the chroot environment
chroot_cleanup() {
    current_function

    local BASE_PATH=$(get_base_path "${1}")
    local FILE FILENAME DIRS_TO_REMOVE FILES_TO_REMOVE

    toggle_shell_options eu

    # Directories to completely remove
    declare -a DIRS_TO_REMOVE=(
        "/patches"
        "/rootcopy"
        "/rootcopy-install"
        "/.cache"
        "/etc/systemd/system/timers.target.wants"
        "/root/.cache"
        "/root/.local/share/mc"
        "/usr/share/doc/*"
        "/usr/share/gnome/help"
        "/usr/share/icons/elementaryXubuntu-dark"
        "/usr/share/icons/gnome/256x256"
        "/usr/share/info/*"
        "/usr/share/man/??"
        "/usr/share/man/*_*"
    )

    # Single files to remove
    declare -a FILES_TO_REMOVE=(
        "/kernel.conf"
        "/${PACKAGE_VARIANT}.list"
        "/${PACKAGE_VARIANT}-l10n.list"
        "/build"
        "/cleanup"
        "/minioslib"
        "/install"
        "/package.list"
        "/postinstall"
        "/preinstall"
        "/root/.bash_history"
        "/root/.wget-hsts"
        "/usr/share/images/fluxbox/debian-squared.jpg"
        "/var/lib/dhcp/dhclient.leases"
    )

    # Customize FILES_TO_REMOVE and DIRS_TO_REMOVE for module mode
    if [ "$1" == "module" ]; then
        FILES_TO_REMOVE+=("/etc/machine-id")
        #DIRS_TO_REMOVE+=("/boot" "/dev" "/proc" "/sys" "/tmp" "/run")
    fi

    # Iterate and remove directories
    for DIR in "${DIRS_TO_REMOVE[@]}"; do
        rm -rf "${BASE_PATH}${DIR}" 2>/dev/null
    done

    # Iterate and remove files
    for FILE in "${FILES_TO_REMOVE[@]}"; do
        rm -f "${BASE_PATH}${FILE}" 2>/dev/null
    done

    # Remove files within specific directories and with specific wildcards
    if [ "${1}" = "core" ] || [[ "${MODULE}" == *"xorg"* ]] || [[ "${MODULE}" == *"gui-base"* ]]; then
        rm -f "${BASE_PATH}"/usr/share/applications/*.desktop 2>/dev/null
    fi
    if [[ "${MODULE}" == *"${LIVEKITNAME}"* ]]; then
        rm -rf "${BASE_PATH}"/var/lib/dpkg 2>/dev/null
    fi
    rm -f "${BASE_PATH}"/var/backups/* 2>/dev/null
    rm -f "${BASE_PATH}"/var/cache/apt/*.bin 2>/dev/null
    rm -f "${BASE_PATH}"/var/cache/debconf/* 2>/dev/null
    rm -f "${BASE_PATH}"/var/cache/debconf/*-old 2>/dev/null
    rm -f "${BASE_PATH}"/var/cache/fontconfig/* 2>/dev/null
    rm -f "${BASE_PATH}"/var/cache/ldconfig/* 2>/dev/null
    rm -f "${BASE_PATH}"/var/lib/apt/lists/*Packages 2>/dev/null
    rm -f "${BASE_PATH}"/var/lib/apt/lists/*Translation* 2>/dev/null
    rm -f "${BASE_PATH}"/var/lib/apt/lists/*InRelease 2>/dev/null
    rm -f "${BASE_PATH}"/var/lib/apt/lists/deb.* 2>/dev/null
    rm -f "${BASE_PATH}"/var/lib/dpkg/*-old 2>/dev/null

    # Remove all files except xrdp.log within /var/log/ and its subdirectories
    find "${BASE_PATH}"/var/log/ -type f ! -name "xrdp.log" -exec rm -f {} \; 2>/dev/null

    if [ "${REMOVE_DPKG_DB}" = "true" ]; then
        if [ -d "${BASE_PATH}"/usr/share/icons ]; then
            rm -rf "${BASE_PATH}"/var/lib/dpkg 2>/dev/null
        fi
    fi

    if [ "${REMOVE_LARGE_ICONS}" = "true" ]; then
        if [ -d "${BASE_PATH}"/usr/share/icons ]; then
            find "${BASE_PATH}"/usr/share/icons/ -name 256x256 -o -name 512x512 -o -name 1024x1024 2>/dev/null | xargs rm -rf
        fi
    fi

    KEEP_ALL_LOCALES="${KEEP_ALL_LOCALES:-false}" # By default, delete locales

    if [ "${MULTILINGUAL}" = "true" ]; then
        LOCALES_TO_KEEP=("${!LOCALES[@]}")
    else
        LOCALES_TO_KEEP=("$LOCALE")
    fi
    if [ "${KEEP_ALL_LOCALES}" != "true" ]; then
        if [ "$LOCALE" = "en_US" ] && [ "$MULTILINGUAL" != "true" ]; then
            rm -rf "${BASE_PATH}"/usr/share/fluxbox/nls/??* 2>/dev/null
            rm -rf "${BASE_PATH}"/usr/share/locale/?? 2>/dev/null
            rm -rf "${BASE_PATH}"/usr/share/locale/??_* 2>/dev/null
            rm -rf "${BASE_PATH}"/usr/share/locale/??@* 2>/dev/null
            rm -rf "${BASE_PATH}"/usr/share/locale/??? 2>/dev/null
            rm -rf "${BASE_PATH}"/usr/share/i18n/locales/*_* 2>/dev/null
        else
            # Paths and patterns where excess locales will be deleted
            PATHS_AND_PATTERNS_TO_CLEAN=(
                "${BASE_PATH}"/usr/share/fluxbox/nls/??*
                "${BASE_PATH}"/usr/share/locale/??
                "${BASE_PATH}"/usr/share/locale/??_*
                "${BASE_PATH}"/usr/share/locale/??@*
                "${BASE_PATH}"/usr/share/locale/???
                "${BASE_PATH}"/usr/share/i18n/locales/*_*
            )

            # Iterate over all paths and patterns
            for PATH_AND_PATTERN in "${PATHS_AND_PATTERNS_TO_CLEAN[@]}"; do
                information "Checking files in ${PATH_AND_PATTERN}..."

                # Iterate over all files and directories matching the current pattern
                for FILE in ${PATH_AND_PATTERN}; do
                    # Get the name of the file or directory without the full path
                    FILENAME=$(basename "$FILE")

                    # Check if this file or directory should be kept
                    KEEP=false
                    for ITEM in "${LOCALES_TO_KEEP[@]}"; do
                        ITEM_CODE=$(echo $ITEM | cut -d_ -f1)
                        if [[ "${FILENAME}" == "${ITEM}"* ]] || [[ "${FILENAME}" == "${ITEM_CODE}" ]]; then
                            KEEP=true
                            break
                        fi
                    done

                    # If the file or directory should not be kept, delete it
                    if ! ${KEEP}; then
                        information "Deleting ${FILE}..."
                        rm -rf "${FILE}"
                    else
                        information "Keeping ${FILE}..."
                    fi
                done
            done

            information "Locale cleanup completed."
        fi
    else
        information "Skipping locale cleanup due to KEEP_ALL_LOCALES variable setting."
    fi

    if [ -z "${BASE_PATH}" ]; then
        uncompress_files "${BASE_PATH}"/etc/alternatives
        uncompress_files "${BASE_PATH}"/usr/share/man

        remove_broken_links "${BASE_PATH}"/etc/alternatives
        remove_broken_links "${BASE_PATH}"/usr/share/man
    fi

    toggle_shell_options eu
}

# This function handles the installation of necessary packages for the host system.
hostreq_pkg_list() {
    PKGS_TO_INSTALL=""
    if [ -f "${SCRIPT_DIR}/linux-live/package_lists/required_for_host.list" ]; then
        PKGS_TO_INSTALL=$(grep -vE "^\s*#" "${SCRIPT_DIR}/linux-live/package_lists/required_for_host.list" | tr "\n" " ")
    else
        PKGS_TO_INSTALL="sudo debootstrap genisoimage"
    fi
    if [ "${DISTRIBUTION_ARCH}" = "arm64" ]; then
        PKGS_TO_INSTALL="${PKGS_TO_INSTALL} grub-efi-arm64-bin"
    else
        PKGS_TO_INSTALL="${PKGS_TO_INSTALL} grub-efi-amd64-bin grub-pc-bin"
    fi

    PKGS_TO_INSTALL_FILTERED=""
    for PKG in ${PKGS_TO_INSTALL}; do
        if ! dpkg-query -W -f='${Status}' "${PKG}" 2>/dev/null | grep -q "ok installed"; then
            PKGS_TO_INSTALL_FILTERED="${PKGS_TO_INSTALL_FILTERED} ${PKG}"
        fi
    done

    if [ -n "${PKGS_TO_INSTALL_FILTERED}" ]; then
        echo -e "${YELLOW}=====> installing required software for host system ...${ENDCOLOR}"
        pkg update
        pkg install ${PKGS_TO_INSTALL_FILTERED}
    fi
}

# This function handles the installation of necessary pre-requisite packages.
install_prerequesited_packages() {
    echo -e "${YELLOW}=====> installing prerequested software for chroot system ...${ENDCOLOR}"

    if [ ! -f /etc/apt/apt.conf.d/000MiniOS ]; then
        cat <<'EOF' >/etc/apt/apt.conf.d/000MiniOS
APT::Install-Recommends "0";
APT::Install-Suggests "0";
Acquire::Languages { "none"; }
EOF
    fi

    if [ $DISTRIBUTION = "buster" ]; then
        wget --no-check-certificate https://deb.freexian.com/extended-lts/archive-key.gpg -O elts-archive-key.gpg && mv elts-archive-key.gpg /etc/apt/trusted.gpg.d/freexian-archive-extended-lts.gpg
    fi

    if [ -f "${SCRIPT_DIR}/package_lists/prerequisites.list" ]; then
        pkg update
        echo -e "${YELLOW}=====> upgrading chroot system ...${ENDCOLOR}"
        pkg upgrade
        echo -e "${YELLOW}=====> installing packages ...${ENDCOLOR}"
        install_packages --package-list "${SCRIPT_DIR}/package_lists/prerequisites.list"
    fi

}

# This function installs the core packages within the chroot system.
install_core_packages() {
    current_process
    echo -e "${YELLOW}=====> installing main packages for chroot system ...${ENDCOLOR}"

    export_chroot_variables

    if [ -f /linux-live/scripts/00-core/preinstall ]; then
        chmod +x /linux-live/scripts/00-core/preinstall
        /linux-live/scripts/00-core/preinstall
    fi
    if [ -f /linux-live/scripts/00-core/install ]; then
        chmod +x /linux-live/scripts/00-core/install
        /linux-live/scripts/00-core/install
    fi
    if [ -f /linux-live/scripts/00-core/postinstall ]; then
        chmod +x /linux-live/scripts/00-core/postinstall
        /linux-live/scripts/00-core/postinstall
    fi

    chroot_cleanup core

    pkg autoremove
}

# The chroot_finish_up function performs cleanup tasks within a chroot environment.
# It removes diversions, temporary files, log files, and cache files.
chroot_finish_up() {
    current_process

    # truncate machine id (why??)
    truncate -s 0 /etc/machine-id

    # remove diversion (why??)
    rm /sbin/initctl 2>/dev/null
    dpkg-divert --rename --remove /sbin/initctl

    # clean up useless stuff
    rm -rf /tmp/* ~/.bash_history rm /sbin/initctl 2>/dev/null
    rm -rf ~/.cache rm /sbin/initctl 2>/dev/null
    find /var/log/ -type f | xargs rm -f rm /sbin/initctl 2>/dev/null
    rm -f /etc/ssh/ssh_host* rm /sbin/initctl 2>/dev/null
    rm -f /var/backups/* 2>/dev/null
    rm -f /var/cache/ldconfig/* 2>/dev/null
    rm -f /var/cache/fontconfig/* 2>/dev/null
    rm -f /var/cache/apt/*.bin 2>/dev/null
    rm -f /var/cache/debconf/*-old 2>/dev/null
    rm -f /var/lib/apt/extended_states 2>/dev/null
    rm -f /var/lib/apt/lists/*Packages 2>/dev/null
    rm -f /var/lib/apt/lists/*Translation* 2>/dev/null
    rm -f /var/lib/apt/lists/*InRelease 2>/dev/null
    rm -f /var/lib/apt/lists/deb.* 2>/dev/null
    rm -f /var/lib/dpkg/*-old 2>/dev/null
}

# Function: install_packages
# Description: Installs packages with various conditions and parameters.
# Parameters:
#   -s, --sed-script: Sed script to apply to the package list.
#   -a, --add-packages: List of packages to add.
#   -d, --delete-packages: List of packages to delete.
#   -r, --replace-packages: List of packages to replace (format: oldpkg:newpkg).
#   -l, --package-list: Path to the package list file.
#   -t, --target-release: Target release version.
#   --simulation: Enable simulation mode (no actual installation).
#
# Package List Format:
#   - Each package on a separate line.
#   - Lines starting with '#' are comments.
#   - Special symbols:
#     ! : Mandatory package.
#     || : Logical "OR".
#     && : Logical "AND".
#     + : Positive filter.
#     - : Negative filter.
#
# Example Usage:
#   install_packages -a vim curl -d nano emacs -r oldpkg1:newpkg1 -l /path/to/package.list -t bionic --simulation
#
# Example Package List:
#   # Core packages
#   vim
#   curl
#   # Development packages
#   gcc
#   make
#   # Mandatory package
#   !qemu-kvm
#   # Packages with logical "OR"
#   vim || nano
#   # Packages with logical "AND"
#   gcc && make
#   # Packages with positive filter
#   curl +d=bionic
#   # Packages with negative filter
#   emacs -d=bionic
install_packages() {
    toggle_shell_options u
    local SED_SCRIPT=""
    local ADD_PACKAGES=()
    local DELETE_PACKAGES=()
    local REPLACE_PACKAGES=()
    local PACKAGE_LIST=""
    local TARGET_RELEASE=""
    local PACKAGE_ARRAY=()
    local SIMULATION_MODE=false

    console_colors

    while [[ $# -gt 0 ]]; do
        case "$1" in
        -s | --sed-script)
            shift
            while [[ $# -gt 0 && $1 != -* ]]; do
                SED_SCRIPT+="$1 "
                shift
            done
            ;;
        -a | --add-packages)
            shift
            while [[ $# -gt 0 && $1 != -* ]]; do
                ADD_PACKAGES+=("$1")
                shift
            done
            ;;
        -d | --delete-packages)
            shift
            while [[ $# -gt 0 && $1 != -* ]]; do
                DELETE_PACKAGES+=("$1")
                shift
            done
            ;;
        -r | --replace-packages)
            shift
            while [[ $# -gt 0 && $1 != -* ]]; do
                REPLACE_PACKAGES+=("$1")
                shift
            done
            ;;
        -l | --package-list)
            PACKAGE_LIST="$2"
            shift 2
            ;;
        -t | --target-release)
            TARGET_RELEASE="$2"
            shift 2
            ;;
        --simulation)
            SIMULATION_MODE=true
            shift
            ;;
        *)
            error "Invalid option: $1" >&2
            exit 1
            ;;
        esac
    done

    if [[ "$SIMULATION_MODE" == true ]]; then
        information "Simulation mode."
        echo "DISTRIBUTION=$DISTRIBUTION"
        echo "DISTRIBUTION_ARCH=$DISTRIBUTION_ARCH"
        echo "DISTRIBUTION_TYPE=$DISTRIBUTION_TYPE"
        echo "DISTRIBUTION_PHASE=$DISTRIBUTION_PHASE"
        echo "DESKTOP_ENVIRONMENT=$DESKTOP_ENVIRONMENT"
        echo "PACKAGE_VARIANT=$PACKAGE_VARIANT"
    fi

    if [[ -z "${PACKAGE_LIST}" ]]; then
        if [ -f "${SCRIPT_DIR}/${PACKAGE_VARIANT}.list" ]; then
            PACKAGE_LIST="${SCRIPT_DIR}/${PACKAGE_VARIANT}.list"
        elif [ -f "${SCRIPT_DIR}/package.list" ]; then
            PACKAGE_LIST="${SCRIPT_DIR}/package.list"
        else
            error "There are no lists of packages here."
            exit 1
        fi
    fi

    for PKG in "${DELETE_PACKAGES[@]}"; do
        SED_SCRIPT+="/${PKG}/d;"
    done

    for PKG in "${REPLACE_PACKAGES[@]}"; do
        OLD_PKG="${PKG%%:*}"
        NEW_PKG="${PKG#*:}"
        SED_SCRIPT+="s#${OLD_PKG}#${NEW_PKG}#g;"
    done

    while IFS= read -r LINE; do
        LINE=${LINE%%#*}
        if [[ -n "${LINE// /}" ]]; then
            LINE=$(echo "${LINE}" | sed "${SED_SCRIPT}")
            if [[ -n "${LINE// /}" ]]; then
                PACKAGE_ARRAY+=("${LINE}")
            fi
        fi
    done < <(grep -vE '^\s*#' "${PACKAGE_LIST}")

    PACKAGE_ARRAY+=("${ADD_PACKAGES[@]}")

    if [ ! -e "/var/cache/apt/pkgcache.bin" ]; then
        information "pkgcache.bin cache file missing. Running update of package list..."
        [[ "$SIMULATION_MODE" != true ]] && pkg update
    else
        information "pkgcache.bin cache file detected. Update of package list is not required."
    fi

    PACKAGES_TO_INSTALL=()
    for LINE in "${PACKAGE_ARRAY[@]}"; do
        process_package_conditions "${LINE}"
    done

    if [[ "$SIMULATION_MODE" == true ]]; then
        information "Simulation mode is ON. No packages will be installed. Exiting..."
        exit 1
    else
        pkg install "${PACKAGES_TO_INSTALL[@]}"
    fi

    toggle_shell_options u
}

process_package_conditions() {
    local PACKAGE_CONDITIONS="$1"
    local MANDATORY=false

    if [[ ${PACKAGE_CONDITIONS:0:1} == "!" ]]; then
        PACKAGE_CONDITIONS="${PACKAGE_CONDITIONS:1}"
        MANDATORY=true
    fi

    local ALTERNATIVES
    if [[ "$PACKAGE_CONDITIONS" == *"||"* ]]; then
        IFS='||' read -r -a ALTERNATIVES <<<"$PACKAGE_CONDITIONS"
    else
        ALTERNATIVES=("$PACKAGE_CONDITIONS")
    fi
    for ALTERNATIVE in "${ALTERNATIVES[@]}"; do
        local ALTERNATIVE=$(echo "$ALTERNATIVE" | xargs)
        local CONDITIONS
        if [[ "$ALTERNATIVE" == *"&&"* ]]; then
            toggle_shell_options e
            IFS=$'\n' read -d '' -r -a CONDITIONS < <(echo "$ALTERNATIVE" | awk -F '&&' '{for(i=1;i<=NF;i++) print $i}')
            toggle_shell_options e
        else
            CONDITIONS=("$ALTERNATIVE")
        fi
        local ALL_CONDITIONS_MET=true
        for CONDITION in "${CONDITIONS[@]}"; do
            local CONDITION=$(echo "$CONDITION" | xargs)
            if ! process_condition "$CONDITION" $MANDATORY; then
                ALL_CONDITIONS_MET=false
                break
            fi
        done

        if $ALL_CONDITIONS_MET; then
            return 0
        fi
    done

    if $MANDATORY; then
        exit 1
    fi

    #return 1
}

process_condition() {
    local CONDITION="$1"
    local MANDATORY=$2
    local PACKAGE_NAME
    local PACKAGE_VERSION
    local INCLUDE=true
    local FILTER_STRING=""
    local FILTER

    IFS=' ' read -r PACKAGE_PART CONDITION_STRING <<<"$CONDITION"
    IFS='=' read -r PACKAGE_NAME PACKAGE_VERSION <<<"$PACKAGE_PART"

    if [[ -z "$PACKAGE_NAME" ]]; then
        warning "Invalid package name in condition: $CONDITION"
        return 1
    fi

    IFS=' ' read -r -a FILTERS <<<"$CONDITION_STRING"

    for FILTER in "${FILTERS[@]}"; do
        case "$FILTER" in
        +*)
            FILTER=${FILTER#+}
            if ! check_filter "$FILTER"; then
                INCLUDE=false
                FILTER_STRING+="${RED}[+ ${FILTER}]: Failed${ENDCOLOR}; "
            else
                FILTER_STRING+="${GREEN}[+ ${FILTER}]: Passed${ENDCOLOR}; "
            fi
            ;;
        -*)
            FILTER=${FILTER#-}
            if check_filter "$FILTER"; then
                INCLUDE=false
                FILTER_STRING+="${RED}[- ${FILTER}]: Failed${ENDCOLOR}; "
            else
                FILTER_STRING+="${GREEN}[- ${FILTER}]: Passed${ENDCOLOR}; "
            fi
            ;;
        esac
    done

    if $INCLUDE && check_package "$PACKAGE_NAME" "$PACKAGE_VERSION"; then
        if [[ -n "$FILTER_STRING" ]]; then
            information "Package ${CYAN}$PACKAGE_NAME${ENDCOLOR} will be installed. Filters: ${FILTER_STRING%??}"
        else
            information "Package ${CYAN}$PACKAGE_NAME${ENDCOLOR} will be installed."
        fi
        PACKAGES_TO_INSTALL+=("$PACKAGE_NAME")
        return 0
    else
        if [[ -n "$FILTER_STRING" ]]; then
            warning "Package ${CYAN}$PACKAGE_NAME${ENDCOLOR} will not be installed. Filters: ${FILTER_STRING%??}"
        else
            $MANDATORY && error "Mandatory package ${CYAN}$PACKAGE_NAME${ENDCOLOR} is not found in the repository. Installation aborted." || warning "Package ${CYAN}$PACKAGE_NAME${ENDCOLOR} is not found in the repository and will not be installed."
        fi
        return 1
    fi
}

check_package() {
    local PACKAGE_NAME="$1"
    local PACKAGE_VERSION="$2"

    if $SIMULATION_MODE; then
        return 0
    fi

    if [ "${PACKAGE_NAME}" = "qemu-kvm" ]; then
        if echo $(LANG=C apt-cache --quiet=0 show "${PACKAGE_NAME}" 2>&1) | grep -q 'purely virtual'; then
            return 0
        else
            return 1
        fi
    fi

    if [ -z "${PACKAGE_VERSION}" ]; then
        if echo $(LANG=C apt-cache --quiet=0 policy "${PACKAGE_NAME}" 2>&1) | grep -q 'Candidate: (none)\|Unable to locate package\|No packages found'; then
            return 1
        else
            return 0
        fi
    else
        if echo $(LANG=C apt-cache --quiet=0 madison "${PACKAGE_NAME}" 2>&1) | grep -q "${PACKAGE_VERSION}"; then
            return 0
        else
            warning "Version ${CYAN}${PACKAGE_VERSION}${ENDCOLOR} of package ${CYAN}${PACKAGE_NAME}${ENDCOLOR} is not in the repository, fall back to the available version."
            if echo $(LANG=C apt-cache --quiet=0 policy "${PACKAGE_NAME}" 2>&1) | grep -q 'Candidate: (none)\|Unable to locate package\|No packages found'; then
                return 1
            else
                return 0
            fi
        fi
    fi
}

check_filter() {
    local FILTER="$1"

    case "$FILTER" in
    d=*)
        [[ "${FILTER#d=}" == "$DISTRIBUTION" ]] && return 0
        ;;
    da=*)
        [[ "${FILTER#da=}" == "$DISTRIBUTION_ARCH" ]] && return 0
        ;;
    dt=*)
        [[ "${FILTER#dt=}" == "$DISTRIBUTION_TYPE" ]] && return 0
        ;;
    dp=*)
        [[ "${FILTER#dp=}" == "$DISTRIBUTION_PHASE" ]] && return 0
        ;;
    de=*)
        [[ "${FILTER#de=}" == "$DESKTOP_ENVIRONMENT" ]] && return 0
        ;;
    pv=*)
        [[ "${FILTER#pv=}" == "$PACKAGE_VARIANT" ]] && return 0
        ;;
    esac

    return 1
}

# This function checks for mounted filesystems in chroot and unmounts them.
module_check_mounted() {
    current_function
    if grep -qs "${MODULE_MERGED_DIR}" /proc/mounts || grep -qs "${MODULE_MERGED_DIR}/dev" /proc/mounts || grep -qs "${MODULE_MERGED_DIR}/run" /proc/mounts || grep -qs "${MODULE_MERGED_DIR}/proc" /proc/mounts || grep -qs "${MODULE_MERGED_DIR}/sys" /proc/mounts || grep -qs "${MODULE_MERGED_DIR}/dev/pts" /proc/mounts || grep -qs "${MODULE_MERGED_DIR}/tmp" /proc/mounts; then
        echo -e "${BOLD}${LIGHTYELLOW}Сhroot contains mounted filesystems.${ENDCOLOR}"
        module_unmount_dirs
    fi
}

# This function filters and lists modules based on package variant and other conditions like filter modules configuration.
filter_modules() {
    current_function
    if [ "${PACKAGE_VARIANT}" = "puzzle" ]; then
        FILTER_MODULES="true"
        if ! [[ ${MODULE} =~ ^0[0-9]-* ]]; then
            FILTER_LEVEL="3"
        elif [[ ${MODULE} == "00-"* ]] || [[ ${MODULE} == "01-"* ]]; then
            FILTER_LEVEL="0"
        else
            FILTER_LEVEL=$((${MODULE:1:1} - 1))
        fi
    else
        if [ -f "${SCRIPT_DIR}/linux-live/container_config" ]; then
            read_config "${SCRIPT_DIR}/linux-live/container_config" FILTER_MODULES FILTER_LEVEL
        else
            read_config "${SCRIPT_DIR}/linux-live/config" FILTER_MODULES FILTER_LEVEL
        fi
    fi
    echo -e "=====  ${YELLOW}MODULE=${MODULE}${ENDCOLOR}"
    echo -e "=====  ${YELLOW}FILTER_MODULES=${FILTER_MODULES}${ENDCOLOR}"
    echo -e "=====  ${YELLOW}FILTER_LEVEL=${FILTER_LEVEL}${ENDCOLOR}"
    if [ "${FILTER_MODULES}" = "true" ]; then
        MODULES_LIST=$(ls -1dr $1[0]* | egrep "0[0-${FILTER_LEVEL}]" | tr '\n' ':')
    else
        MODULES_LIST=$(ls -1dr $1[0-9]* | tr '\n' ':')
    fi
}

# This function mounts file systems for modules within a chroot environment.
module_chroot_mount_fs() {
    current_function

    local MODULES BUNDLE FILTER DIR

    unmount_dirs "${WORK_DIR}"

    MODULES_LIST=""
    MODULES=("${WORK_DIR}/image/${LIVEKITNAME}"/*."${BEXT}")
    for ((i = ${#MODULES[@]} - 1; i >= 0; i--)); do
        BUNDLE=$(basename "${MODULES[$i]}" ."${BEXT}")
        mkdir -p "${OVERLAYS}/bundles/${BUNDLE}"
        mount "${MODULES[$i]}" "${OVERLAYS}/bundles/${BUNDLE}"
    done
    toggle_shell_options e
    filter_modules "${OVERLAYS}/bundles/"
    MODULES_LIST=${MODULES_LIST/%:/}
    toggle_shell_options e
    mkdir -p "${MODULE_UPPER_DIR}" "${MODULE_WORK_DIR}" "${MODULE_MERGED_DIR}"
    mount -t overlay overlay -o lowerdir="${MODULES_LIST}",upperdir="${MODULE_UPPER_DIR}",workdir="${MODULE_WORK_DIR}" "${MODULE_MERGED_DIR}"

    setup_chroot_environment "${MODULE_MERGED_DIR}"

    update_resolv_conf "${MODULE_MERGED_DIR}"
}

# This function unmounts directories within a chroot environment.
module_unmount_dirs() {
    current_function

    unmount_dirs "${WORK_DIR}"

    if [ -e "${MODULE_UPPER_DIR}/etc/resolv.conf" ]; then
        rm -f "${MODULE_UPPER_DIR}/etc/resolv.conf"
    elif [ -e "${MODULE_UPPER_DIR}/etc/resolv.conf.bak" ]; then
        rm -f "${MODULE_UPPER_DIR}/etc/resolv.conf.bak"
    fi
}

# Function to clean up the module
module_cleanup() {
    current_function
    unmount_dirs "${WORK_DIR}"
    if [ -d "${MODULE_UPPER_DIR}" ]; then
        rm -rf "${MODULE_UPPER_DIR}"
    fi
}

# Function to finalize chroot environment
module_chroot_finish_up() {
    current_function

    # Truncate machine id (why??)
    chroot "${MODULE_MERGED_DIR}" /bin/bash -x <<EOF
truncate -s 0 /etc/machine-id 2>/dev/null
EOF

    # Remove diversion (why??)
    chroot "${MODULE_MERGED_DIR}" /bin/bash -x <<EOF
    rm /sbin/initctl 2>/dev/null
    dpkg-divert --rename --remove /sbin/initctl 2>/dev/null
EOF

}

# функция для экспорта ядра, если это ядро или модуль Linux
export_kernel() {
    if [[ "${MODULE}" != *"kernel"* ]]; then
        return
    fi

    if [ "${EXPORT_KERNEL}" = "TRUE" ] || [ "${MOVE_KERNEL}" = "TRUE" ]; then
        local OPERATION="copied"
        [ "${MOVE_KERNEL}" = "TRUE" ] && OPERATION="moved"

        current_function
        kernel_variables
        mkdir -p "${KERNEL_DIR}"

        if [ "${KERNEL_BUILD_DKMS}" = "TRUE" ]; then
            KERNEL_MODULE_NAME="${MODULE}-${KERNEL_SUFFIX}-ext-${COMP_TYPE}.${BEXT}"
        else
            KERNEL_MODULE_NAME="${MODULE}-${KERNEL_SUFFIX}-${COMP_TYPE}.${BEXT}"
        fi

        local SOURCE_PATH="${WORK_DIR}/image/${LIVEKITNAME}/${KERNEL_MODULE_NAME}"
        local VMLINUX_PATH="${WORK_DIR}/image/${LIVEKITNAME}/boot/${VMLINUZNAME}"
        local INITRFS_PATH="${WORK_DIR}/image/${LIVEKITNAME}/boot/${INITRFSNAME}"

        if [ -f "${SOURCE_PATH}" ]; then
            ${MOVE_KERNEL} "${SOURCE_PATH}" "${KERNEL_DIR}"

            if [ "${NAMED_BOOT_FILES}" = "TRUE" ]; then
                ${MOVE_KERNEL} "${VMLINUX_PATH}" "${KERNEL_DIR}"
                ${MOVE_KERNEL} "${INITRFS_PATH}" "${KERNEL_DIR}"
            else
                ${MOVE_KERNEL} "${VMLINUX_PATH}" "${KERNEL_DIR}/vmlinuz-${KERNEL_SUFFIX}"
                ${MOVE_KERNEL} "${INITRFS_PATH}" "${KERNEL_DIR}/initrfs-${KERNEL_SUFFIX}.img"
            fi

            information "Kernel files and initrd image have been ${OPERATION} to a folder ${KERNEL_DIR}"
        else
            information "Kernel files could not be found."
        fi
    fi
}

# Function to set kernel related variables
kernel_variables() {
    toggle_shell_options u
    KERNEL_SUFFIX="${KERNEL}"
    toggle_shell_options u

    if [ "${NAMED_BOOT_FILES}" = "true" ]; then
        VMLINUZNAME="vmlinuz-${KERNEL_SUFFIX}"
        INITRFSNAME="initrfs-${KERNEL_SUFFIX}.img"
    else
        VMLINUZNAME="vmlinuz"
        INITRFSNAME="initrfs.img"
    fi
}

# Function to create initial ramdisk file system (initrfs) for a Linux kernel
create_initrfs() {
    if [[ "${MODULE}" == *"kernel"* ]] || [[ "${MODULE}" == *"linux"* ]]; then
        if [ -f "${WORK_DIR}/minios-build.conf" ]; then
            read_config "${WORK_DIR}/minios-build.conf" KERNEL
        fi
        copy_build_scripts "${MODULE_MERGED_DIR}"
        chmod +x "${MODULE_MERGED_DIR}/linux-live/initrfs"
        chroot "${MODULE_MERGED_DIR}" /linux-live/initrfs
        kernel_variables
        if ls "${MODULE_MERGED_DIR}"/boot/vmlinuz-**-**-* 2>/dev/null; then
            cp "${MODULE_MERGED_DIR}"/boot/vmlinuz-**-**-* "${WORK_DIR}/image/${LIVEKITNAME}/boot/${VMLINUZNAME}"
        elif ls "${MODULE_MERGED_DIR}"/boot/vmlinuz-**.**.* 2>/dev/null; then
            cp "${MODULE_MERGED_DIR}"/boot/vmlinuz-**.**.* "${WORK_DIR}/image/${LIVEKITNAME}/boot/${VMLINUZNAME}"
        else
            cp "${MODULE_MERGED_DIR}"/boot/vmlinuz-* "${WORK_DIR}/image/${LIVEKITNAME}/boot/${VMLINUZNAME}"
        fi
        cp "${MODULE_MERGED_DIR}/boot/initrfs.img" "${WORK_DIR}/image/${LIVEKITNAME}/boot/${INITRFSNAME}"
        sed -i "s,/boot/vmlinuz,/boot/${VMLINUZNAME},g;s,/boot/initrfs.img,/boot/${INITRFSNAME},g" "${WORK_DIR}/image/${LIVEKITNAME}/boot/syslinux.cfg"
        sed -i "s,/boot/vmlinuz,/boot/${VMLINUZNAME},g;s,/boot/initrfs.img,/boot/${INITRFSNAME},g" "${WORK_DIR}/image/${LIVEKITNAME}/boot/grub/grub.cfg"
        if [ -d "${MODULE_MERGED_DIR}/linux-live" ]; then
            rm -rf "${MODULE_MERGED_DIR}/linux-live"
        fi
    fi
}

# Function to export language packs if multilingual support is enabled in configuration
export_locales() {
    local FILE BASE_NAME NEW_NAME
    if [[ "${MODULE}" == *"langpack"* ]]; then
        if [ "${MULTILINGUAL}" = "true" ]; then
            current_function
            mkdir -p "${LOCALES_DIR}"
            for FILE in "${MODULE_UPPER_DIR}"/*langpack*; do
                cp "${FILE}" "${LOCALES_DIR}/$(basename "${FILE}")"
            done
        fi
    fi
}

# handle() - A generic function to manage different types of files
#
# Usage:
# handle SRC DEST TYPE
#
# Where:
# SRC - The source file or directory
# DEST - The destination file or directory
# TYPE - The type of file operation, which can be one of the following:
# - regular: Copies a regular file, if it exists, and sets executable permissions
# - directory: Copies the content of a directory, if it is not empty
# - symlink: Removes a symbolic link, if it exists
# - module: Copies kernel module files, if the source directory exists
handle() {
    local TYPE=$1
    local SRC=$2
    if [ "${TYPE}" != "symlink" ]; then
        local DEST=$3
    fi

    case $TYPE in
    file)
        if [ -f "${SRC}" ]; then
            cp "${SRC}" "${DEST}"
            chmod +x "${DEST}"
        fi
        ;;
    directory)
        if [ "$(ls -A "${SRC}" 2>/dev/null)" != "" ]; then
            mkdir -p "${DEST}"
            (cd "${SRC}" && cp --parents -afr * "${DEST}/")
        fi
        ;;
    symlink)
        if [ -L "${SRC}" ]; then
            rm -f "${SRC}"
        fi
        ;;
    module)
        if [ -d "${SRC}" ]; then
            mkdir -p "${DEST}"
            handle directory "${SRC}" "${DEST}"
        fi
        ;;
    *)
        error "Invalid type: ${TYPE}"
        return 1
        ;;
    esac
}

# Build modules function: This function builds modules for the system.
build_modules() {
    current_process

    local VMLINUZNAME INITRFSNAME KERNEL_SUFFIX PACKAGE VERSION BUNDLE FILENAME KEEP_DIRS MODULE_DIR TARGET_DIR DIR

    if [ "${CONTAINER_TYPE}" = "1" ]; then
        if [ ! -f "/.minios-live" ]; then
            setup_host
        fi
    fi

    ENVIRONMENTS="${SCRIPT_DIR}/linux-live/environments/${DESKTOP_ENVIRONMENT}"
    OVERLAYS="${WORK_DIR}/overlays"

    cd "${ENVIRONMENTS}"

    for MODULE in *; do
        if (ls "${WORK_DIR}/image/${LIVEKITNAME}/"*."${BEXT}" 2>/dev/null | grep -q "${MODULE}" 2>/dev/null); then
            echo -e "${MAGENTA}${MODULE}${ENDCOLOR} module building is skipped, because it is already present in the image folder." #| fold -w 80 -s
        elif ([[ "${MODULE}" == *"kernel"* ]] || [[ "${MODULE}" == *"linux"* ]]) && [ "${KERNEL_TYPE}" = "none" ]; then
            return
        else
            MODULE_UPPER_DIR="${OVERLAYS}/${MODULE}-upper"
            MODULE_WORK_DIR="${OVERLAYS}/${MODULE}-work"
            MODULE_MERGED_DIR="${OVERLAYS}/${MODULE}-merged"
            EXCLUDE_MODULE_FILE="${WORK_DIR}/squashfs-${MODULE}-exclude"
            #generate_squashfs_exclude_file "${EXCLUDE_MODULE_FILE}" "${MODULE_UPPER_DIR}"

            module_cleanup

            module_chroot_mount_fs

            # run pre-install script
            handle file "${ENVIRONMENTS}/${MODULE}/preinstall" "${MODULE_MERGED_DIR}/preinstall"
            if [ -f "${MODULE_MERGED_DIR}/preinstall" ]; then
                echo -e "=====> ${GREEN}preinstall${ENDCOLOR} script is executing ..."
                chroot_run "${MODULE_MERGED_DIR}" /preinstall
            fi

            # copy files
            if [ -f "${ENVIRONMENTS}/${MODULE}/build" ]; then
                handle directory "${ENVIRONMENTS}/${MODULE}/rootcopy-install" "${MODULE_MERGED_DIR}/rootcopy-install"
            else
                handle directory "${ENVIRONMENTS}/${MODULE}/rootcopy-install" "${MODULE_MERGED_DIR}"
            fi

            # run install script
            handle file "${ENVIRONMENTS}/${MODULE}/install" "${MODULE_MERGED_DIR}/install"
            if [ -f "${MODULE_MERGED_DIR}/install" ]; then
                handle file "${ENVIRONMENTS}/${MODULE}/cleanup" "${MODULE_MERGED_DIR}/cleanup"
                handle file "${ENVIRONMENTS}/${MODULE}/${PACKAGE_VARIANT}.list" "${MODULE_MERGED_DIR}/${PACKAGE_VARIANT}.list"
                handle file "${ENVIRONMENTS}/${MODULE}/${PACKAGE_VARIANT}-l10n.list" "${MODULE_MERGED_DIR}/${PACKAGE_VARIANT}-l10n.list"
                handle file "${ENVIRONMENTS}/${MODULE}/package.list" "${MODULE_MERGED_DIR}/package.list"
                echo -e "=====> ${GREEN}install${ENDCOLOR} script is executing ..."
                chroot_run "${MODULE_MERGED_DIR}" /install
            fi

            if [[ "${MODULE}" == *"kernel"* ]]; then
                if [ -f "${MODULE_UPPER_DIR}/minios-build.conf" ]; then
                    read_config "${MODULE_UPPER_DIR}/minios-build.conf" KERNEL KERNEL_ARCH KERNEL_VERSION KERNEL_BUILD_ARCH
                    update_config "${WORK_DIR}/minios-build.conf" KERNEL KERNEL_ARCH KERNEL_VERSION KERNEL_BUILD_ARCH
                fi
            fi

            if [ -f "${ENVIRONMENTS}/${MODULE}/build" ]; then
                module_unmount_dirs

                chroot_cleanup module

                # run external actions
                if [ "$(ls -A "${MODULE_UPPER_DIR}" 2>/dev/null)" != "" ]; then
                    mkdir -p "${OVERLAYS}/tmp"
                    mkmod_corefs "${MODULE_UPPER_DIR}"
                    time mksquashfs "${MODULE_UPPER_DIR}" "${OVERLAYS}/tmp/${MODULE}-stock.${BEXT}" -comp lz4 -b 1024K -always-use-fragments -noappend -quiet -progress || exit
                    mv "${OVERLAYS}/tmp/${MODULE}-stock.${BEXT}" "${MODULE_UPPER_DIR}/${MODULE}-stock.${BEXT}"
                    cd "${MODULE_UPPER_DIR}" || exit 1
                    unsquashfs "${MODULE}-stock.${BEXT}"
                else
                    echo -e "${MAGENTA}${MODULE_UPPER_DIR}${ENDCOLOR} is empty. Nothing to do."
                fi

                module_chroot_mount_fs

                # run build script
                if [ -f $ENVIRONMENTS/$MODULE/build ]; then
                    cp $ENVIRONMENTS/$MODULE/build $MODULE_MERGED_DIR/build
                    chmod +x $MODULE_MERGED_DIR/build
                    if [ "$(ls -A $ENVIRONMENTS/$MODULE/patches 2>/dev/null)" != "" ]; then
                        mkdir -p $MODULE_MERGED_DIR/patches
                        (cd $ENVIRONMENTS/$MODULE/patches && cp --parents -afr * $MODULE_MERGED_DIR/patches/)
                    fi

                    chroot_run "${MODULE_MERGED_DIR}" /build

                    build_boot
                fi
            fi

            if [ -f "${ENVIRONMENTS}/${MODULE}/is_dkms_build" ]; then
                read_config "${WORK_DIR}/minios-build.conf" KERNEL

                handle symlink "${MODULE_UPPER_DIR}/usr/lib/modules/${KERNEL}/build"
                handle symlink "${MODULE_UPPER_DIR}/usr/lib/modules/${KERNEL}/source"
                handle symlink "${MODULE_UPPER_DIR}/lib/modules/${KERNEL}/build"
                handle symlink "${MODULE_UPPER_DIR}/lib/modules/${KERNEL}/source"

                toggle_shell_options e
                handle module "${MODULE_UPPER_DIR}/usr/lib/modules/${KERNEL}" "${MODULE_UPPER_DIR}/squashfs-root/usr/lib/modules/${KERNEL}"
                handle module "${MODULE_UPPER_DIR}/lib/modules/${KERNEL}" "${MODULE_UPPER_DIR}/squashfs-root/lib/modules/${KERNEL}"
                toggle_shell_options e

                if [ "${MODULE}" = "01-kernel" ]; then
                    cd "${MODULE_UPPER_DIR}/squashfs-root" || exit 1
                    if ls initrd* 2>/dev/null; then
                        rm initrd*
                    fi
                    if ls vmlinuz* 2>/dev/null; then
                        rm vmlinuz*
                    fi
                    if [ "${KERNEL_MODULES_ONLY}" = "true" ]; then
                        bash -c "
                        shopt -s extglob
                        if [ -d "${MODULE_UPPER_DIR}/squashfs-root/usr/lib/modules" ]; then
                            rm -rf !(usr)
                            cd "${MODULE_UPPER_DIR}/squashfs-root/usr"
                            rm -Rf !(lib)
                            cd "${MODULE_UPPER_DIR}/squashfs-root/usr/lib"
                            rm -Rf !(modules)
                        elif [ -d "${MODULE_UPPER_DIR}/squashfs-root/lib/modules" ]; then
                            rm -Rf !(lib)
                            cd "${MODULE_UPPER_DIR}/squashfs-root/lib"
                            rm -Rf !(modules)
                        fi
                        cd "${MODULE_UPPER_DIR}/squashfs-root"
                        shopt -u extglob"
                    else
                        rm -Rf boot dev etc proc run sys tmp var/lib/apt
                    fi
                fi
                time mksquashfs "${MODULE_MERGED_DIR}/squashfs-root" $MODULE_MERGED_DIR/${MODULE}.${BEXT} -comp ${COMP_TYPE} ${ADDITIONAL_COMP_OPTS} -b 1024K -always-use-fragments -noappend -quiet -progress || exit

                rm -f "${MODULE_UPPER_DIR}/minios-build.conf"
            fi

            # copy files
            if [[ "${MODULE}" == *"${LIVEKITNAME}"* ]]; then
                #cp "${SCRIPT_DIR}/linux-live/minioslib" "${MODULE_MERGED_DIR}/usr/lib/minios/libminioslive"
                mkdir -p "${MODULE_MERGED_DIR}/usr/share/minios"
                mkdir -p "${MODULE_MERGED_DIR}/etc/minios"
                if [ -f "${SCRIPT_DIR}/linux-live/container_config" ]; then
                    cp "${SCRIPT_DIR}/linux-live/container_config" "${MODULE_MERGED_DIR}/etc/minios/config"
                else
                    cp "${SCRIPT_DIR}/linux-live/config" "${MODULE_MERGED_DIR}/etc/minios/config"
                fi
            fi
            if [ ! -f "${ENVIRONMENTS}/${MODULE}/build" ]; then
                if [ "$(ls -A "${ENVIRONMENTS}/${MODULE}/rootcopy-postinstall" 2>/dev/null)" != "" ]; then
                    (cd "${ENVIRONMENTS}/${MODULE}/rootcopy-postinstall" && cp --parents -afr * "${MODULE_MERGED_DIR}/")
                fi
            else
                if [ "$(ls -A "${ENVIRONMENTS}/${MODULE}/rootcopy-postinstall" 2>/dev/null)" != "" ]; then
                    mkdir -p "${MODULE_MERGED_DIR}/rootcopy-postinstall"
                    (cd "${ENVIRONMENTS}/${MODULE}/rootcopy-postinstall" && cp --parents -afr * "${MODULE_MERGED_DIR}/rootcopy-postinstall/")
                fi
            fi

            # run post-install script
            if [ -f "${ENVIRONMENTS}/${MODULE}/postinstall" ]; then
                echo -e "=====> ${GREEN}postinstall${ENDCOLOR} script is executing ..."
                cp "${ENVIRONMENTS}/${MODULE}/postinstall" "${MODULE_MERGED_DIR}/postinstall"
                chmod +x "${MODULE_MERGED_DIR}/postinstall"
                chroot_run "${MODULE_MERGED_DIR}" /postinstall
            fi

            if [ ! -f "${ENVIRONMENTS}/${MODULE}/build" ]; then
                toggle_shell_options e
                chroot_run "${MODULE_MERGED_DIR}" uncompress_files /etc/alternatives
                chroot_run "${MODULE_MERGED_DIR}" uncompress_files /usr/share/man
                chroot_run "${MODULE_MERGED_DIR}" remove_broken_links /etc/alternatives
                chroot_run "${MODULE_MERGED_DIR}" remove_broken_links /usr/share/man
                toggle_shell_options e
            fi

            if [ -f "${MODULE_UPPER_DIR}/.package" ] && [ "${PACKAGE_VARIANT}" = "puzzle" ]; then
                read_config "${MODULE_UPPER_DIR}/.package" PACKAGE VERSION
                MODULE_NUMBER=$(echo "${MODULE}" | awk -F- '{ print $1 }')
                MODULE_NAME="${MODULE_NUMBER}-${PACKAGE}-${VERSION}"
                rm "${MODULE_UPPER_DIR}/.package"
            else
                MODULE_NAME=${MODULE}
                if [ -f "${MODULE_UPPER_DIR}/.package" ]; then
                    rm "${MODULE_UPPER_DIR}/.package"
                fi
            fi

            # run external actions
            if [ -f "${ENVIRONMENTS}/${MODULE}/build" ]; then
                if [[ "${MODULE}" == *"kernel"* ]] || [[ "${MODULE}" == *"linux"* ]]; then
                    cp "${MODULE_UPPER_DIR}/${MODULE}.${BEXT}" "${WORK_DIR}/image/${LIVEKITNAME}/${MODULE}-${KERNEL_SUFFIX}-${COMP_TYPE}.${BEXT}"
                else
                    cp "${MODULE_UPPER_DIR}/${MODULE}.${BEXT}" "${WORK_DIR}/image/${LIVEKITNAME}/${MODULE_NAME}-${DISTRIBUTION_ARCH}-${COMP_TYPE}.${BEXT}"
                fi
            fi

            module_chroot_finish_up

            module_unmount_dirs

            if [ ! -f "${ENVIRONMENTS}/${MODULE}/build" ]; then
                chroot_cleanup module
            fi

            mkmod_corefs "${MODULE_UPPER_DIR}"

            if [ ! -f "${ENVIRONMENTS}/${MODULE}/build" ]; then
                if [ ! -f "${WORK_DIR}/image/${LIVEKITNAME}/${MODULE}-${COMP_TYPE}.${BEXT}" ]; then
                    if [ "$(ls -A "${MODULE_UPPER_DIR}" 2>/dev/null)" != "" ]; then
                        if [[ "${MODULE}" == *"kernel"* ]] || [[ "${MODULE}" == *"linux"* ]]; then
                            FILENAME="${WORK_DIR}/image/${LIVEKITNAME}/${MODULE}-${KERNEL_SUFFIX}-${COMP_TYPE}.${BEXT}"
                        else
                            FILENAME="${WORK_DIR}/image/${LIVEKITNAME}/${MODULE_NAME}-${DISTRIBUTION_ARCH}-${COMP_TYPE}.${BEXT}"
                        fi

                        time mksquashfs ${COREFS} "${FILENAME}" -comp "${COMP_TYPE}" ${ADDITIONAL_COMP_OPTS} -b 1024K -always-use-fragments -noappend -quiet -progress || exit
                    else
                        echo -e "${MAGENTA}${MODULE_UPPER_DIR}${ENDCOLOR} is empty. Nothing to do."
                    fi
                else
                    echo -e "${RED}!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!${ENDCOLOR}"
                    echo -e "Please remove ${MAGENTA}${WORK_DIR}/image/${LIVEKITNAME}/${MODULE}.${BEXT}${ENDCOLOR} if you want to build ${MAGENTA}${MODULE}${ENDCOLOR}."
                    echo -e "${RED}!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!${ENDCOLOR}"
                fi
            fi
        fi
        export_kernel
        export_locales
    done
}

repack_module() {
    local OLD_MODULE="${MODULE}"
    MODULE="${MODULE%"-${OLD_COMP_TYPE}.${BEXT}"}"
    if [ "${COMP_TYPE}" = "${OLD_COMP_TYPE}" ]; then
        echo "The module is already in the required compression format." && exit
    fi
    unsquashfs "${OLD_MODULE}"
    mkmod_corefs "${WORK_DIR}/image/${LIVEKITNAME}/squashfs-root"
    time mksquashfs ${COREFS} "${WORK_DIR}/image/${LIVEKITNAME}/${MODULE}-${COMP_TYPE}.${BEXT}" -comp "${COMP_TYPE}" ${ADDITIONAL_COMP_OPTS} -b 1024K -always-use-fragments -noappend -quiet -progress || exit

    rm -rf "${WORK_DIR}/image/${LIVEKITNAME}/squashfs-root"
    rm -f "${WORK_DIR}/image/${LIVEKITNAME}/${OLD_MODULE}"
}

repack_modules() {
    current_process

    cd "${WORK_DIR}/image/${LIVEKITNAME}/"

    for MODULE in *; do
        if [ -d "${WORK_DIR}/image/${LIVEKITNAME}/squashfs-root" ]; then
            rm -rf "${WORK_DIR}/image/${LIVEKITNAME}/squashfs-root"
        fi
        echo "Repacking module ${MODULE}"
        cd "${WORK_DIR}/image/${LIVEKITNAME}" || exit 1
        OLD_COMP_TYPE=$(unsquashfs -s "${MODULE}" | grep Compression | awk -F" " '{ print $2 }')
        repack_module
    done
}

# ==================== BATCH BUILD FUNCTIONS ======================

generate_config_files() {
    cp -f "${SCRIPT_DIR}/linux-live/config" "${SCRIPT_DIR}/linux-live/container_config"

    VARS=("DISTRIBUTION_TYPE" "DISTRIBUTION" "DISTRIBUTION_ARCH" "DESKTOP_ENVIRONMENT" "PACKAGE_VARIANT" "KERNEL_TYPE" "KERNEL_BPO" "KERNEL_AUFS" "COMP_TYPE" "LOCALE" "MULTILINGUAL")
    for VAR in "${VARS[@]}"; do
        sed -i -e "/${VAR}=/s/=.*/=\"${!VAR}\"/" "${SCRIPT_DIR}/linux-live/container_config"
    done
    sed -i -e "/TIMEZONE=/s/=.*/=\"${TIMEZONE//\//\\/}\"/" "${SCRIPT_DIR}/linux-live/container_config"
}

container_run() {
    if [ ! "$(${container_engine} ps -q -f name=mlc-${DISTRIBUTION}-${DESKTOP_ENVIRONMENT}-${PACKAGE_VARIANT}${LANGID}${AUFS_SUFFIX}${BPO_SUFFIX}-${KERNEL_TYPE}-${DISTRIBUTION_ARCH}-${COMP_TYPE})" ]; then
        if [ "$(${container_engine} ps -aq -f status=exited -f name=mlc-${DISTRIBUTION}-${DESKTOP_ENVIRONMENT}-${PACKAGE_VARIANT}${LANGID}${AUFS_SUFFIX}${BPO_SUFFIX}-${KERNEL_TYPE}-${DISTRIBUTION_ARCH}-${COMP_TYPE})" ]; then
            # cleanup
            ${container_engine} rm -f ${CONTAINER_NAME}
        fi
        generate_config_files
        # run your container
        ${container_engine} run -d --log-driver=journald --name ${CONTAINER_NAME} --privileged -v /dev:/dev --device-cgroup-rule 'b 7:* rmw' -v /build:/build \
            -e DISTRIBUTION_TYPE="${DISTRIBUTION_TYPE}" \
            -e DISTRIBUTION="${DISTRIBUTION}" \
            -e DESKTOP_ENVIRONMENT="${DESKTOP_ENVIRONMENT}" \
            -e PACKAGE_VARIANT="${PACKAGE_VARIANT}" \
            -e DISTRIBUTION_ARCH="${DISTRIBUTION_ARCH}" \
            -e COMP_TYPE="${COMP_TYPE}" \
            -e LOCALE="${LOCALE}" \
            -e MULTILINGUAL="${MULTILINGUAL}" \
            -e TIMEZONE="${TIMEZONE}" \
            -e USER_NAME="live" \
            -e USER_PASSWORD="evil" \
            -e ROOT_PASSWORD="toor" \
            -e DEFAULT_TARGET="graphical" \
            -e ENABLE_SERVICES="" \
            -e DISABLE_SERVICES="" \
            -e AUTOLOGIN="true" \
            -e SSH_KEY="authorized_keys" \
            -e SCRIPTS="false" \
            -e CLOUD="false" \
            -e HIDE_CREDENTIALS="false" \
            -e LINK_USER_DIRS="false" \
            -e SYSTEM_TYPE="puzzle" \
            -e DISTRIBUTION_VARIANT="minbase" \
            -e BATCH="true" \
            -e BUILD_TEST_ISO="false" \
            -e ROOTFS_TARBALL="false" \
            -e DEBIAN_FRONTEND_TYPE="noninteractive" \
            -e APT_CMD="apt-get" \
            -e UNION_BUILD_TYPE="overlayfs" \
            -e SYSTEMNAME="MiniOS" \
            -e USE_ROOTFS="true" \
            -e USE_ROOTFS="true" \
            -e REMOVE_OLD_ISO="true" \
            -e REMOVE_SOURCES="true" \
            -e SKIP_SETUP_HOST="true" \
            -e DOWNLOAD_SOURCES="false" \
            -e REMOVE_DPKG_DB="false" \
            -e KERNEL_MODULES_ONLY="true" \
            -e REMOVE_LARGE_ICONS="true" \
            -e FILTER_MODULES="false" \
            -e FILTER_LEVEL="3" \
            -e KERNEL_TYPE="${KERNEL_TYPE}" \
            -e KERNEL_BPO="${KERNEL_BPO}" \
            -e KERNEL_AUFS="${KERNEL_AUFS}" \
            -e KERNEL_BUILD_DKMS="${KERNEL_BUILD_DKMS}" \
            -e NAMED_BOOT_FILES="false" \
            -e EXPORT_KERNEL="true" \
            -e MOVE_KERNEL="false" \
            -e EXPORT_LOGS="false" \
            -e BUILD_FROM_SNAPSHOT="false" \
            -e SNAPSHOT_DATE="20220223T214737Z" \
            local/mlc /build/minios-live/install -
    fi
}

container_status_check() {
    RUN=$(${container_engine} inspect ${CONTAINER_NAME} --format='{{.State.Status}}')
}
container_build_finish() {
    if [ -f "${SCRIPT_DIR}/linux-live/container_config" ]; then
        rm -f "${SCRIPT_DIR}/linux-live/container_config"
    fi
    mkdir -p "${SCRIPT_DIR}/logs"
    ${container_engine} logs ${CONTAINER_NAME} >&"${SCRIPT_DIR}/logs/${CONTAINER_NAME}.log"
    START=$(docker inspect --format='{{.State.StartedAt}}' ${CONTAINER_NAME})
    STOP=$(docker inspect --format='{{.State.FinishedAt}}' ${CONTAINER_NAME})
    START_TIMESTAMP=$(date --date=${START} +%s)
    STOP_TIMESTAMP=$(date --date=${STOP} +%s)
    echo "Container runtime count: ${CONTAINER_NAME} $((${STOP_TIMESTAMP} - ${START_TIMESTAMP})) seconds"
    echo "Container runtime count: ${CONTAINER_NAME} $((${STOP_TIMESTAMP} - ${START_TIMESTAMP})) seconds" >>"${SCRIPT_DIR}/logs/${CONTAINER_NAME}.log"
    if [ "$(${container_engine} ps -aq -f status=exited -f name=${CONTAINER_NAME})" ]; then
        # cleanup
        ${container_engine} rm -f ${CONTAINER_NAME} && rm -rf "/build/minios-live/build/${DISTRIBUTION}-${PACKAGE_VARIANT}-${DISTRIBUTION_ARCH}"
    fi
    if [ -d "/build/minios-live/build/${DISTRIBUTION}-${PACKAGE_VARIANT}-${DISTRIBUTION_ARCH}" ]; then
        echo "/build/minios-live/build/${DISTRIBUTION}-${PACKAGE_VARIANT}-${DISTRIBUTION_ARCH} exists"
    fi
}

container_build_run() {
    local AUFS_SUFFIX BPO_SUFFIX
    DISTRIBUTION_TYPE="debian"
    MULTILINGUAL="false"

    while [[ "$#" -gt 0 ]]; do
        case $1 in
        -d | --distribution)
            DISTRIBUTION="$2"
            shift
            ;;
        -e | --desktop-environment)
            DESKTOP_ENVIRONMENT="$2"
            shift
            ;;
        -p | --package-variant)
            PACKAGE_VARIANT="$2"
            shift
            ;;
        -a | --distribution-arch)
            DISTRIBUTION_ARCH="$2"
            shift
            ;;
        -k | --kernel-type)
            KERNEL_TYPE="$2"
            shift
            ;;
        -b | --kernel-bpo)
            KERNEL_BPO="$2"
            shift
            ;;
        -u | --kernel-aufs)
            KERNEL_AUFS="$2"
            shift
            ;;
        -B | --kernel-build-dkms)
            KERNEL_BUILD_DKMS="$2"
            shift
            ;;
        -c | --comp-type)
            COMP_TYPE="$2"
            shift
            ;;
        -l | --locale)
            LOCALE="$2"
            shift
            ;;
        -T | --timezone)
            TIMEZONE="$2"
            shift
            ;;
        *)
            echo "Unknown parameter passed: $1"
            exit 1
            ;;
        esac
        shift
    done

    if [ ${MULTILINGUAL} = "true" ]; then
        LANGID=""
    else
        LANGID="-$(echo ${LOCALE} | cut -d_ -f1)-"
    fi

    case $1 in
    trusty | xenial | bionic | focal | groovy | hirsute | impish | jammy | kinetic | lunar | mantic | noble)
        DISTRIBUTION_TYPE="ubuntu"
        ;;
    esac

    if [ ${KERNEL_BPO} = "true" ]; then
        BPO_SUFFIX="-bpo"
    fi
    if [ ${KERNEL_AUFS} = "true" ]; then
        AUFS_SUFFIX="-aufs"
    fi
    CONTAINER_NAME="mlc-${DISTRIBUTION}-${DESKTOP_ENVIRONMENT}-${PACKAGE_VARIANT}-${LANGID}${AUFS_SUFFIX}${BPO_SUFFIX}-${KERNEL_TYPE}-${DISTRIBUTION_ARCH}-${COMP_TYPE}"

    if [ ! "$(${container_engine} ps -q -f name=${CONTAINER_NAME})" ]; then
        if [ "$(${container_engine} ps -aq -f status=exited -f name=${CONTAINER_NAME})" ]; then
            # cleanup
            ${container_engine} rm -f ${CONTAINER_NAME}
        fi
        # run your container
        container_run
    fi

    container_status_check
    if [ "${RUN}" = "running" ]; then
        echo "Container ${CONTAINER_NAME} has been started."
    fi

    while container_status_check; do
        if [ "${RUN}" = "running" ]; then
            printf "."
            sleep 5
        else
            if [ "$(${container_engine} inspect ${CONTAINER_NAME} --format='{{.State.ExitCode}}')" != "0" ] 2>/dev/null; then
                printf "\nERROR: Container ${CONTAINER_NAME} stopped with error. Exit code $(${container_engine} inspect ${CONTAINER_NAME} --format='{{.State.ExitCode}}')\n"
                container_build_finish
            else
                printf "\nContainer ${CONTAINER_NAME} stopped, proceeding...\n"
                container_build_finish
            fi
            break
        fi
    done
}
